Navigation

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.

Influences

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

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

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.

Compositionality

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 intersted 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

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 it’s 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 so far to remove 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:

  • 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.

As can be seen in the previous 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):

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.

Summary

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.

Posted Tuesday, March 6th, 2012 at 17:35
Written by: | Filed Under Category: Development
You can leave a response, or trackback from your own site.

20

Responses to “Symfony2 Form Architecture”

Greg Militello

I see a few issues with other frameworks adopting the Symfony2 form code, however most are minor in scope and should be things that can be worked around:

1.) Licensing. Code has to be distributed in a way that licenses are compatible. Open source does not mean license compatible.

2.) Pride. This is going to be hard to overcome, but if you are the author of “UberCoolFramework(tm)” you may have a hard time using Symfony2′s forms. Yes this sucks. But the issue remains.

3.) Code maintenance concerns. A developer for another framework may be concerned about getting the features he wants in the code base, or that his pull requests will be given any credence.

Of course NONE of these issues causes an unsolvable problem. (in fact there may be other issues as well). But all of them can be dealt with, but only if both sides of the equation want to work on it.

The license of all Symfony2 components is MIT, which should be sufficiently liberal for all use cases.

There isn’t much to do about 2) aside from shaming projects that prefer to reimplement the similar API’s with similar approaches to make it more embarrassing :)

For 3) this is of course a real issue. Git and other similar tools and the services build on top help a lot here since every patch send up stream is also a fork to use in the mean time (or worst case indefinitely). Track records of projects with dealing both with contributions but also maintaining BC will be a key factor here in the long term.

Drak

@Greg – The MIT license mixes with all OSS licenses and is corporate closed source compatible too.

Greg Militello

@Drak,

Yes I know. While it *shouldn’t* be a problem in this case, it would be if someone used “UberCoolFramework(tm)”’s code released under a different license. It is a generic problem with code reuse, no so much when you look at MIT.

Now that ZF2 is on Github and does not require a CLA there is really no reason not to integrate this and other components. Sad, really. These two camps should work together where possible instead of this nonsense. Symfony2 uses many external libraries and supports that type of architecture. Many wheels.

Thank you for this interesting post Bernhard. I didn’t have time to look into symfony2 yet, but the form area is surly a topic where much can be improved, and standardised. Your effort is welcome.

It’s really sad that both Zend and Symfony ignore each other. I remember when Symfon2 used Zend_Log in early days, but then replaced it with monolog. They are both on github and should work together whenever that have sense. That woul be great for PHP community.

First of all I am a Zend Framework 1 developer, who has been enviously eyeing Symfony2 for some time, but cannot use it in my day-to-day work because of other developers in my team.

However I have just completed an integration activity to use Symfony2 validators with Doctrine2 in ZF1, and I have to say it was an eye opening experience, since each of the Symfony components is stand alone so you can use it without the others, which is excellent foresight and can only be compared to what Rod Johnson of Springsource did for Java a few years ago. Disruptive innovation to simplify development.

Egos aside, ZF2 is still in beta while Symfony2 is already in production and growing by leaps and bounds, so the view and form layers are things that I see they should collaborate on, just like Symfony uses Doctrine2 Annotations support.

Drupal has chosen Symfony2 as the basis for the CMS, but is contributing rigorous documentation standards to the project which in my case is a +10000.

Great Post to learn about the Symfony 2 form, and how it was born.
I don’t have any disrespect for Symfony2 or ZF2 .
If the API name is same, doesn’t means the inner codes are also same . I don’t even know how many of you tried Symfony2 form to make use of a Standalone component .
The people who says its standalone is not the real fact, they really need to make some changes in-order to incorporate and which may be hard to look, and learn why the implementation was made.
I recently wrote a blog post regarding my experience working with Symfony2 form , and also have a link to a form component which seems almost standalone .
You can read the post over http://harikt.com/standalone-form-component-for-php
I am open to hear your feedback too .

I’m curious…it was mentioned above that Symfony used to use Zend_Log in its logging, but that it no longer does. So I see a lot of “Zend Framework is duplicating what Symfony does” but not a lot of “Zend Framework’s ____ component does this better, Symfony devs should use that”. Last time I checked, interoperability is a two-way street.

@chris: Monolog provides a fair number of things that Zend_Log didn’t. Not sure if at the time of the switch it was ported to namespaces yet. BTW Monolog is a PHP port of a very mature Python library (same applies to Assetic too btw).

Now as for what ZF has that Symfony2 doesn’t. Quite a lot outside of the MVC.

For example the Feed component in ZF is probably the best library for handling RSS/Atom feeds since it surpassed the PEAR library with the last big iteration.

Then there is still like the Services components which mostly provide stuff that I have not seen anyone in the Symfony2 community.

The biggest problem with third parties adopting Symfony forms is how they handle validation and form data.

Everything is geared towards hydrating objects with Symfony’s validation attached there to (either through DI, annotations, or what have you) and it’s very unwieldy to operate outside of a Symfony2 context.

I recently integrated Symfony2 Form into a SlimPHP application and while it got off the ground fairly well (documentation on integration outside of Symfony is very weak, took some hacking to figure out how to get it up and running) the validation was a mess. I had to bring in Symfony2 Validation (which honestly is okay) but integrated it in a weird fashion.

If you check out this pastie: http://pastie.org/3543000 that is what I had to do to validate this checkout form.

Preferably validation constraints would be attached to each field at scaffold time, since validators are ALREADY associated with a field. The architecture decision wouldn’t be as painful if setting a field required validated on the backend (it does not, it only sets the required html5 attribute, you have to add the notblank validator by hand).

Not to completely knock it, it integrated well (if a bit painful due to lack of documentation). The validation workflow however is going to be the biggest pain they have and is an area that needs major improvement.

Bernhard

@Hari: It is true that the whole, feature-complete form component (including all of the extensions) has many dependencies. But this is not really avoidable. The form component contentrates on what it can do best – abstracting and processing forms. It doesn’t care about validation, output escaping, templating etc., and uses external libraries for that purpose. If you don’t like the external libraries, you can implement your own layer that uses your own library.

@Daniel: I’m aware of these problems and want to fix them soon.

Hey Bernhard, thanks for your very good post about Symfony2 forms architecture.

I wish, all the symfony components would have such a great architectural-documentation.

Keep up the good work!

Christoph

[...] Symfony2 Form Architecture : via @mojoLyon [...]

Dave Pendejo

Coming from Rails, this is absurdly complicated. Why not leave this to what it is – a template level problem. Why do I need to create a new model just to render a form?! There should be the model of my data and `n` views of it however I want without having to create _another_ data model to back up each of those.

Bernhard

@Dave: Because this is not a template level problem only. Forms need to convert input from the format used in the POST request to the format used in your application. The POST format itself depends on the HTML controls used in the template. A date, for example, could be entered using a single text field, three text fields (year, month, date) or three select fields. Regardless of the rendering, your application wants to receive a normalized value (e.g. a DateTime object).

Therefore, the rendering and data conversion are highly coupled. Symfony2 simply provides an architecture to abstract this coupling in a flexible way.

Thanks for the excellent work you are doing on the Form component! :)

[...] create your framework else you did nothing in your life!!!!!Also you might read this for example: – http://webmozarts.com/2012/03/06/symfony2-form-architecture/ which is an example of how things work in PHP and how well people colaborate in making things [...]

[...] Zend no utiliza ninguno de los componentes liberados por el proyecto Symfony, a pesar de que se ha demostrado que en casos como en el de los formularios, las similitudes son más que [...]

Leave a Reply

 

Additional Resources