Symfony2 Form Architecture

Symfony2 features a brand-new Form component that, to my knowledge, supersedes most existing PHP form libraries in functionality and extensibility (not counting the still lacking, native JavaScript support). It has been in development for two years, even though I was already thinking about it since 2009 and earlier. It is becoming more and more stable recently, with a first completely stable release expected for Symfony 2.2.

This post was partially triggered by the release of the new Zend Framework 2 Form RFC because I think that a lot of duplicated effort is going on there. I completely understand that Zend Framework 2 needs a form layer that is tailored to the components delivered by the framework. The purpose of this post is to demonstrate that the Symfony2 Form component is perfectly suited for this requirement. Symfony2-specific functionality can be unplugged, leaving only the raw core dealing with form processing and abstraction. As a replacement, functionality can be developed for supporting Zend's or any other framework's components.

Creating a generic form library that elegantly solves all the various use-cases that can be found in web form construction and processing has been a challenging, long-lasting and complex task that is not over yet. Cooperating and continuing development from this common base on seems like a big chance to make form handling in PHP more powerful – and easier – than it has ever been before.

The post starts with crediting all the other great frameworks and form libraries that influenced this work. Then I would like to introduce you to the key aspects of the Form component before continuing to describe its high and low-level architecture.

This post is not intended to show off the usage or killer features of the Form component. If you are looking for this, you can find examples and explanations in the Form documentation. Neither will this post explain what steps are necessary to use the Form component without Symfony2. This also has been covered, for example in this Gist.


The Form component has been influenced by many other frameworks written in different languages, including symfony 1, Zend Framework 1, Django, Ruby on Rails, Struts and JSF. Apart from that, it shows many similarities with Formlets, WUI and iData, form libraries written for the functional languages Links, Curry and Clean.

Key Aspects

The key aspects of the Form component are:

  • Abstraction
  • Extensibility
  • Compositionality
  • Separation of Concerns
  • Model Binding
  • Dynamic Behavior

I will give a short explanation for each of this aspects before I continue to explain how the component's architecture realizes them.


Abstraction describes the ability to take any part of a form – or even the whole form – and put it into a reusable data structure. Consider a form with three drop down boxes to select the day, month and year for a date. First, you need code that generates the HTML with all of its option tags. Second, you need code that converts from the application's data type (for example, PHP's DateTime) to the view's representation (which option is selected?) and back. If you add another date selector to a form in your application, you need to duplicate and adapt all of that code.

Abstraction solves this problem by providing suitable data structures for describing and reusing your code.


Extensibility refers to two main concepts related to abstraction:

  1. Specialization is a logical consequence of abstraction. When it is possible to abstract functionality into generic data structures, it should also be possible to extend these data structures into custom, specialized ones. A simple example is to extend the above date selector to also show selectors for the time. Without the ability to specialize the existing date selector, a large part of its functionality needs to be rewritten.

  2. Mixins are an orthogonal concept to specialization. Assume that you want to change all existing fields to include an asterisk ("*") in their label if they require user input. Doing so by using specialization is a tedious task, because it requires you to extend every existing field with a custom one, implementing the same new functionality. Mixins, on the other hand, allow to attach functionality to existing objects without the need to specialize them. As a bonus, the added functionality is inherited by all descendants in the inheritance tree.

Extensibility also refers to more indepth extensiblity by means of events, which will be discussed later.


If we examine the last examples a bit more, we discover that there is no relevant difference between fields (complex ones, such as in the example before, and primitive ones, such as a text input tag) and forms. Both fields and forms

  1. accept default values from the model (an array, a date, a string…)
  2. convert the value to a representation suitable for use in the view
  3. render HTML
  4. accept values submitted by the user
  5. convert these values back to the model's format
  6. optionally perform validation

We can implement fields and forms using the same fundamental data structure. By adding compositionality – the ability to nest this data structure into itself (see the Composite pattern) – we can create forms of arbitrary complexity. Instead of forms and fields, we will talk about forms and their children from now on. Once that a form has children, it also needs to

  1. forward (map) its default value (an array or an object) to its children
  2. extract (also map) the submitted value of each child back into the original array/object

Separation of Concerns

We can group the tasks in the above list to several, distinct responsibilities:

  • Data Transformation
  • HTML Generation (the View)
  • Validation
  • Data Mapping

These responsibilities should be implemented by decoupled components with clearly defined interfaces. As a result, any of these components can be replaced by a custom implementation, such as a custom view or validator layer.

Model Binding

In many cases, forms directly relate to structures that have already been described otherwise in the domain model. Consider a form to submit the profile information of a user. Consider further that these profiles are stored in a table in your database. The table has information about the properties stored in the profile, about the types of these properties, their default values and their constraints. Ideally, your application also features a class Profile that is mapped to this database table with an ORM such as Doctrine 2. This class may exhibit more information about the profile, for example, that a profile can be related to any number of subjects that the user is interested in. These subjects must be selected from a list that is stored in a configuration file.

Usually, the information listed here (we will call it metadata) must be replicated in the form layer. The user must know what properties he can edit, the form must display appropriate HTML widgets that correspond to the types of the properties, the user must know which fields may not be left empty and so on. This is why creating forms usually sucks.

Model Binding tries to change this situation. It refers to two ideas:

  1. reuse existing metadata during form construction in order to reduce duplication of code and configuration

  2. read default values from a domain object (an instance of Profile) and write the submitted values back into the object

Dynamic Behavior

Last but not least, forms should support dynamic behavior. Gone are the times were you could statically code your forms on the server and avoid security issues by making sure that every submitted form corresponds to the predefined structure. Nowadays, client-side JavaScripts change the DOM of forms in order to enhance usability.

Just consider a tabular form. Each column contains fields of the same type, each row represents an object on the server. Little buttons allow to delete or to add new rows. Whenever the form is submitted, the server must adjust the form's model to match the deleted and added rows in order to successfully process and validate it.

Dynamic behavior shouldn't be restricted to tabular forms though. Suitable mechanisms in the architecture should allow reactions to any kind of change on the client. Unfortunately, this problem isn't addressed by many libraries.

High-Level Architecture

The high-level architecture of the Symfony2 Form component.

Let me outline the high-level architecture of forms in Symfony2. A their core lies the Form component. This component provides the basic architecture for defining and processing forms and uses Symfony2's Event Dispatcher internally for processing events. On top of the component lie a series of pluggable extensions:

  • The Core extension provides all field definitions (called form types) implemented by the framework.
  • The Validation extension integrates the Symfony2 Validator to implement form validation.
  • The DI extension adds support for Symfony2's Dependency Injection component.
  • The CSRF extension adds CSRF protection to forms.
  • The Doctrine 2 extension (shipped with the Doctrine bridge) adds a Doctrine-specific drop down field and provides components that let forms know about Doctrine metadata.

The topmost layer contains the components responsible for rendering HTML. Symfony2 provides two such components: One for rendering forms in Twig (shipped with the Twig bridge) and another for rendering it with its PHP Templating component (contained in FrameworkBundle).

The most interesting fact for other frameworks here is that every component apart from Form is replaceable. A custom extension could be written to support Zend Validator, another could be written for Smarty and so on. You could even go as far as removing the Core extension and write an own set of basic fields. Even the underlying Event Dispatcher can be replaced by writing a custom one that implements Symfony2's EventDispatcherInterface. You win a lot of flexibility compared to little loss.

Low-Level Architecture

This section continues to discuss the internal architecture of the Form component. As mentioned before, a form and all of its children can be represented by the same data structure that implements the Composite pattern. In the Form component, this data structure is described by the FormInterface. The main implementation of FormInterface is the class Form, which uses three components to do its work:

  • A data mapper distributes the data of a form to its children and merges the data of the children back into the form's data. The default data mapper allows forms to load their values both from arrays and objects or object graphs. After the form's submission, the new values are written back into the original data structure.
  • Two chains of data transformers convert values between different representations. Data transformers guarantee to output values of predefined types to your application, regardless of the format used to display and modify the values in the view.
  • An event dispatcher allows you to execute custom code at predefined points during form processing. It enables you to adapt the form's structure to match the submitted data, or to filter, modify or validate the submitted data and so on.

These components are passed to the constructor of Form and cannot be changed after construction in order to avoid corruption of the form's state. Because the constructor signature is quite long and complicated, a form builder simplifies the construction of Form instances.

The form view is the view representation of a form. This means that you never deal with Form instances in the template, but with FormView instances. These store additional, view-specific inforrmation, such as HTML names, IDs and so on.

The following UML diagram illustrates the architecture.

The UML class diagram of the Symfony2 Form component

As can be seen in the diagram, a form has three different representations throughout its lifecycle:

  • During construction, it is represented by a hierarchy of FormBuilder objects.
  • In the controller, it is represented by a hierarchy of Form objects.
  • In the view, it is represented by a hierarchy of FormView objects.

Because the configuration of form builders and form views is repetitive, Symfony2 implements form types that group such configuration. Form types support dynamic inheritance, meaning that they can extend different base types, depending on the options passed at the the construction of a form. The following diagram illustrates all types that come bundled with the Symfony2 extensions (green types are provided by the Core extension, yellow types by additional ones):

Symfony2 form type hierarchy

Mixins, as described before, are supported in Symfony2 by so-called type extensions. These type extensions can be attached to existing form types and add additional behavior. Symfony2, for example, contains type extensions for adding CSRF protection to the "form" type (and consequently all of its subtypes).

A form factory retrieves the type hierarchy from the loaded extensions and uses them to configure new FormBuilder and FormView objects. It is important to know that this configuration itself can be controlled by user-provided options. For example, the "choice" type supports an option "choices" in which all selectable values need to be passed.

The last important concept in the Form component is that of type guessers. Type guessers try to derive the type and options of a field in the form based on the metadata available for the domain object backing the form (if any). For example, if a property of the object is configured to be a one-to-many-relation to a model Tag, type guessers automatically configure this property to be represented by a multiple-choice field with all Tag instances loaded by default. This concept is similar to ModelForms in Django. The main difference is that your application can use various type guessers to use metadata from different sources instead of just relying on the ORM definition. Symfony2, for example, ships with three guessers: One for reading Doctrine2 metadata, one for Propel metadata and a last one for reading metadata of the Symfony2 validator.

The concepts described in the last paragraphs are summarized again in the following UML diagram.

UML class diagram of the form factory


As I have tried to show in this post, the Symfony2 Form component features a carefully engineered architecture that takes many important aspects of modern form processing into account.

It solves the problem of abstraction, specialization and mixins by providing a dynamic inheritance tree of form types and form type extensions. It solves the compositionality problem by distributing the work and responsibility of processing a form among all of its elements. It offers a clear separation of concerns in order to easily replace different layers of the component. It achieves model binding by involving the existing domain model metadata into the construction of a form and by reading from and writing into domain objects directly. And it supports dynamic behavior by offering events at predefined points during its processing that can be handled by custom listeners, such as for validation or filtering.

Interested? Explore the code. Play with it. And help us to integrate it into your favourite framework.

Check out the trainings section for Symfony forms trainings. At the moment, reduced Early Bird prices are available for trainings in Barcelona and Kiev.