Form Creation

Using PHP8 Attributes or DocBlock Annotations

Creating a complete form solution can often be tedious: you'll create a domain model object, an input filter for validating it, a form object for providing a representation for it, and potentially a hydrator for mapping the form elements and fieldsets to the domain model. Wouldn't it be nice to have a central place to define all of these?

Annotations allow us to solve this problem. With v3, laminas-form supports two different types of annotations: PHP8 attributes and traditional DocBlock annotations. For new projects, PHP8 attributes are recommended.

Using PHP8 Attributes

General information on PHP attributes are available in the PHP documentation. Besides the obvious requirement of PHP 8.0 or newer, there are no further dependencies for using PHP attributes for form creation.

To use attributes, add them to your class and/or properties. The attribute names will be resolved according to the import statements in your class; as such, you can make them as long or as short as you want depending on what you import.

Here is an example:

use Laminas\Filter\StringTrim;
use Laminas\Form\Annotation;
use Laminas\Form\Element\Email;
use Laminas\Hydrator\ObjectPropertyHydrator;
use Laminas\Validator\Regex;
use Laminas\Validator\StringLength;

#[Annotation\Name("user")]
#[Annotation\Hydrator(ObjectPropertyHydrator::class)]
class User
{
    #[Annotation\Exclude]
    public $id;

    #[Annotation\Filter(StringTrim::class)]
    #[Annotation\Validator(StringLength::class, options: ["min" => 1, "max" => 25])]
    #[Annotation\Validator(Regex::class, options: ["pattern" => "/^[a-zA-Z][a-zA-Z0-9_-]{0,24}$/"])]
    #[Annotation\Attributes(["autofocus" => true])]
    #[Annotation\Options(["label" => "Username:"])]
    public $username;

    #[Annotation\Type(Email::class)]
    #[Annotation\Options(["label" => "Your email address:"])]
    public $email;
}

The above will hint to the attribute builder to create a form with name "user", which uses the hydrator Laminas\Hydrator\ObjectPropertyHydrator. That form will have two elements, "username" and "email". The "username" element will have an associated input that has a StringTrim filter, and two validators: a StringLength validator indicating the username is between 1 and 25 characters, and a Regex validator asserting it follows a specific accepted pattern. The form element itself will have an attribute "autofocus", and a label "Username:". The "email" element will be of type Laminas\Form\Element\Email, and have the label "Your email address:".

To use the above, we need an object of Laminas\Form\Annotation\AttributeBuilder:

use Laminas\Form\Annotation\AttributeBuilder;

$builder = new AttributeBuilder();
$form    = $builder->createForm(User::class);

Using DocBlock Annotations

Besides PHP8-only attributes, laminas-form continues to support traditional DocBlock annotations with v3, as they have been supported with v2 already. Using them is suitable for legacy projects or if you need support for PHP 7.

To use DocBlock annotations, include them in your class and/or property docblocks. Annotation names will be resolved according to the import statements in your class; as such, you can make them as long or as short as you want depending on what you import.

Installation Requirements

DocBlock annotations require Doctrine's annotation parser doctrine\annotations as a peer dependency, which contains an annotation parsing engine. You need to manually install it using Composer:

$ composer require doctrine/annotations

Here is the same example from above using DocBlock annotations:

use Laminas\Form\Annotation;

/**
 * @Annotation\Name("user")
 * @Annotation\Hydrator("Laminas\Hydrator\ObjectPropertyHydrator")
 */
class User
{
    /**
     * @Annotation\Exclude()
     */
    public $id;

    /**
     * @Annotation\Filter("StringTrim")
     * @Annotation\Validator("StringLength", options={"min":1, "max":25})
     * @Annotation\Validator("Regex", options={"pattern":"/^[a-zA-Z][a-zA-Z0-9_-]{0,24}$/"})
     * @Annotation\Attributes({"autofocus":true})
     * @Annotation\Options({"label":"Username:"})
     */
    public $username;

    /**
     * @Annotation\Type("Email")
     * @Annotation\Options({"label":"Your email address:"})
     */
    public $email;
}

To create a form based on the above annotations, in contrast to the AttributeBuilder from the previous section, we now need to use Laminas\Form\Annotation\AnnotationBuilder. The usage, however, is the same:

use Laminas\Form\Annotation\AnnotationBuilder;

$builder = new AnnotationBuilder();
$form    = $builder->createForm(User::class);

At this point, you have a form with the appropriate hydrator attached, an input filter with the appropriate inputs, and all elements.

List of Supported Annotations

AllowEmpty

Marks an input as allowing an empty value. This annotation does not require a value.

/**
 * @AllowEmpty
 */
protected $myProperty;
#[AllowEmpty]
protected $myProperty;

Attributes

Used to specify the form, fieldset, or element attributes. This annotation requires an associative array of values. For DocBlock annotations, the array has to be in JSON format.

/**
 * @Attributes({"id":"my_property_element","class":"laminas_form"}
 */
protected $myProperty;
#[Attributes(["id" => "my_property_element", "class" => "laminas_form"])]
protected $myProperty;

ContinueIfEmpty

Indicate whether the element can be submitted when it is empty. A boolean value is expected, defaulting to true. If @Required is set to false, @ContinueIfEmpty(true) or simply @ContinueIfEmpty()needs to be specified to allow the field to be empty.

/**
 * @ContinueIfEmpty()
 */
protected $myProperty;
#[ContinueIfEmpty()]
protected $myProperty;

ComposedObject

Specify another object with annotations to parse. Typically, this is used if a property references another object, which will then be added to your form as an additional fieldset. Expects a string value indicating the class for the object being composed. To compose a collection, the isCollection parameter needs to be set to true and additional options can be passed into the collection. options must be an associative array, for DocBlock annotations encoded as JSON.

/**
 * @ComposedObject("Namespace\Model\ComposedObject")
 */
protected $myProperty;

/**
 * @ComposedObject("Namespace\Model\ComposedObject", isCollection=true, options={"count":2})
 */
protected $myOtherProperty;
#[ComposedObject(\Namespace\Model\ComposedObject::class)]
protected $myProperty;

#[ComposedObject(\Namespace\Model\ComposedObject::class, isCollection: true, options: ["count" => 2])]
protected $myOtherProperty;

ErrorMessage

Specify the error message to return for an element in the case of a failed validation. Expects a string value.

/**
 * @ErrorMessage("This is a customized error message.")
 */
protected $myProperty;
#[ErrorMessage("This is a customized error message.")]
protected $myProperty;

Filter

Used to provide a specification for a filter to use on a given element. Expects a name pointing to a string filter name or class, and optionally further options to pass to the constructor of the filter. options must be an associative array, for DocBlock annotations encoded as JSON.

Additionally, you can use the priority argument to modify the order of the filters in the filter chain.

This annotation may be specified multiple times.

/**
 * @Filter("Boolean", options={"casting":true}, priority=-100)
 */
protected $myProperty;

/**
 * @Filter("Laminas\Filter\Boolean", options={"casting":true})
 */
protected $myOtherProperty;
#[Filter("Boolean", options: ["casting" => true]), priority=-100]
protected $myProperty;

#[Filter(\Laminas\Filter\Boolean::class, options: ["casting" => true])]
protected $myOtherProperty;

Through the FilterPluginManager of laminas-filter, both short names (like Boolean) and fully-qualified class names are supported.

Flags

Additional flags to pass to the fieldset or form composing an element or fieldset; these are usually used to specify the name or priority. The annotation expects an associative array, for DocBlock annotations encoded as JSON.

/**
 * @Flags({"priority": 100})
 */
protected $myProperty;
#[Flags(["priority" => 100])]
protected $myProperty;

Hydrator

Specify the hydrator class to use for this given form or fieldset. A string value is expected.

/**
 * @Hydrator("Laminas\Hydrator\ObjectPropertyHydrator")
 */
protected $myProperty;
#[Hydrator(\Laminas\Hydrator\ObjectPropertyHydrator::class)]
protected $myProperty;

InputFilter

Specify the input filter class to use for this given form or fieldset. A string value is expected.

/**
 * @InputFilter("Laminas\InputFilter\InputFilter")
 */
protected $myProperty;
#[InputFilter(\Laminas\InputFilter\InputFilter::class)]
protected $myProperty;

Input

Specify the input class to use for this given element. A string value is expected.

/**
 * @Input("Laminas\InputFilter\Input")
 */
protected $myProperty;
#[Input(\Laminas\InputFilter\Input::class)]
protected $myProperty;

Instance

Specify an object class instance to bind to the form or fieldset.

/**
 * @Instance("Namespace\Model\Entity")
 */
protected $myProperty;
#[Instance(\Namespace\Model\Entity::class)]
protected $myProperty;

Name

Specify the name of the current element, fieldset, or form. A string value is expected.

/**
 * @Name("my_property")
 */
protected $myProperty;
#[Name("my_property"]
protected $myProperty;

Options

Options to pass to the fieldset or form that are used to inform behavior — things that are not attributes; e.g. labels, CAPTCHA adapters, etc. The annotation expects an associative array, which for DocBlock annotations needs to be JSON encoded.

/**
 * @Options({"label": "Username:"})
 */
protected $myProperty;
#[Options(["label" => "Username:"])]
protected $myProperty;

Required

This annotations indicates whether an element is required. A boolean value is expected. By default, all elements are required, so this annotation is mainly present to allow disabling a requirement.

/**
 * @Required(false)
 */
protected $myProperty;
#[Required(false)]
protected $myProperty;

Type

Indicates the class to use for the current element, fieldset, or form. A string value is expected. See the list of available elements.

/**
 * @Type("Email")
 */
protected $myProperty;
#[Type("Email")]
protected $myProperty;

#[Type(\Laminas\Form\Element\Email::class)]
protected $myOtherProperty;

Through the FormElementManager of laminas-form, both short names (like Email) and fully-qualified class names are supported.

Validator

Used to provide a specification for a validator to use on a given element. Expects a name pointing to a string validator name or class, and optionally further options to pass to the constructor of the validator. options must be an associative array, for DocBlock annotations encoded as JSON.

Additionally, you can use the breakChainOnFailure and the priority argument to modify the validator chain.

This annotation may be specified multiple times.

/**
 * @Validator("StringLength", options={"min":3, "max":25}, breakChainOnFailure=true)
 * @Validator("Laminas\Validator\Regex", options={"pattern": "/^[a-zA-Z]/"})
 */
protected $myProperty;
#[Validator("StringLength", options: ["min" => 3, "max" => 25]), breakChainOnFailure: true]
#[Validator(\Laminas\Validator\Regex::class, options: ["pattern" => "/^[a-zA-Z]/"])]
protected $myProperty;

Through the ValidatorPluginManager of laminas-validator, both short names (like StringLength) and fully-qualified class names are supported.