Symfony Forms, Entities, and Validation

In a previous post, I discussed some of the design patterns behind Symfony 2’s Form component that make it so fast and stable. As requested by Luis Cordova from Craft it Online, I have done further research into how Symfony 2 deals with forms, validation, and entities. Much of the information in this post can be learned from the online documentation, and I highly recommend reading it.

Forms (Symfony\Component\Form)

Forms are created through a Factory service (form.factory), which subsequently uses a Builder object, to automate the creation of fields and assembly of the Form Composite tree. The best way to illustrate and explain this process is to walk through an example usage scenario.

$myForm = $this->createForm(new MyType(), $entity, array(...));

Symfony’s Controller class (Symfony\<wbr />Bundle\<wbr />FrameworkBundle\<wbr />Controller\<wbr />Controller) defines the createForm method shortcut, which accepts the following parameters: a string identifier or an instance of a Type class, an entity to which the form will bind its information, and an array of options to pass to the Type’s buildForm() function. The last two parameters, the entity and the options array, are optional. If no entity is provided, then the data can be retrieved by calling $form->getData() after binding.

Controller::createForm() delegates to FormFactory::<wbr />create(). FormFactory acts as a Façade to the Form component by automating and abstracting much of the creation process. FormFactory::<wbr />create() then delegates to a FormBuilder object, which creates the form recursively for each of the nested Type objects. Afterwards, the FormFactory returns the completed Form tree by calling $builder->getForm().

class MyType extends AbstractType {...}

This is the definition of a Type class that will control how a node on the Form tree will be constructed by the builder. Note that Symfony 2 discourages users from directly defining Form subclasses: using the builder reduces dependency on the Form’s interface and allows its internal structure to vary as Symfony evolves without affecting the user’s code.

public function buildForm(FormBuilder $bldr, array $opts) {...}

This function is a member of the MyType class and is the callback for when a node of type MyType is created. The form factory passes a builder to this callback so this node’s children can be created (even recursively, if necessary by calling $fieldOrSubForm->buildForm($bldr, array(...))). The options are passed from the parent node (or the constructor if this particular Type object was instantiated) and can be used to dynamically configure form elements.

$bldr->add('name', 'text', array('required' => true));

This statement would reside in the definition of MyType::buildForm() and gives me the opportunity to explain how the builder works. The builder’s add function creates a child on the current Form node with the given arguments: name, type, options.

When the builder creates a child node, it sets the new node’s parent reference to the current node that called for the child’s construction. The builder is actually a lot more complicated than how I have explained it here: it uses lazy loading so that it does not create any child objects until it absolutely has to. Calling $builder->getForm() triggers this mechanism. The resulting Form object will allow for data binding and validation and acts as a wrapper for the data object underneath.

return array('view' => $form->createView());

Because the Form object only serves as a wrapper to an entity, array, or other data form, it cannot be rendered into HTML as-is. To resolve this, the controller passes an instance of FormView to the templating engine at render-time. A FormView is a PHP abstraction of the HTML input tags and provides a way for the templating engine to iterate over the fields in the form.

In summary, the FormFactory is the main interface for the Form component; the FormBuilder is responsible for adding fields (Types) to the Form object being built; the Form is a lightweight composite wrapper for the data object that stores the information, and the FormView encapsulates information pertinent to rendering HTML input fields.

Entities

Dynamic web applications depend heavily on storing and retrieving data. Symfony has altered the traditional norm in favor of Domain Driven Design.

Entities are nothing more than plain-old PHP classes that mimic the properties and behavior of real-life objects. They do not extend any base Entity class and are thus entirely self-contained. This is another reason Symfony is so fast and lightweight. Entities help the Symfony framework feel more like working with a statically typed object-oriented language like Java or C++.

Entities are persisted by the Doctrine ORM. The ORM understands how to communicate with the entities because of the metadata information associated with the fields to be saved and by conventional getter and setter names. I prefer using annotations because I can define mapping and validation metadata for each field in the same file. I am also amused by the fact that Doctrine (and other Symfony components) can read and cache the PHPdoc comments that are ignored by the PHP interpreter. Symfony also provides ways to configure entity metadata using YAML, XML, or PHP files.

Validation (Symfony\Component\Validator)

Validation, the act of checking data legitimacy, has traditionally been closely integrated with forms: when a user submits data, a validation script checks that all required fields have been filled, certain numeric or date values are within a predetermined range, and email addresses are correctly formatted. Validation also applies on a coding level: a RESTful web service could submit information directly into the controller without the use of an HTML form; unit tests populate entities directly through setter methods.

Validation must be made available to both Forms and Entities. Symfony 2 solves this problem by extracting the validation subsystem into its own Validator component. According to the Symfony 2 online documentation, “This component is based on the JSR303 Bean Validation specification.”

Like Doctrine, the Validator component knows how to validate an object based on metadata, which can be defined on the Entity using Annotations or in separate YAML, XML, or PHP configuration files. Regardless of how the metadata is defined, the Validator caches it for faster performance.

There are two ways to use the Validator component: either directly or through the Form component. Using it directly is very straightforward. From the controller level, the Validator service can be obtained by calling $this->get('validator'). Inside a Unit test, the Validator is created with ValidatorFactory::buildDefault()->getValidator(). Once we have a Validator, we can pass it an entity to be validated:

$validator = $this->get('validator');
$errors = $validator->validate($entity);

When binding data to a Form, the Form object automatically transfers the data to the underlying entity object and calls the Validation component to check it.

Back