Introduction to the FormHandler library

Written by Pim on Saturday October 3, 2015 - Comment - Permalink
Tags: symfony2, form, php - Category: php

The Symfony2 Form component is probably the most complex Symfony2 component available. The basic implementation of a form can be done in controllers but this gets chaotic very fast. To get a nice and simple structure, I have developed the FormHandler library.

I won't discuss the Symfony2 form component in this blog but if you want some more information about this component I kindly refer you to the documentation. The component is extensively covered in the Symfony2 book, Symfony2 cookbook and as framework agnostic component.

So, What is the FormHandler library?

The name already indicates that the library will handle a form. It provides an interface and abstract implementation how to handle a form in a separate abstraction layer instead of in a controller. This keeps your controllers thin, simple and unit testable. All the business logic is implemented in the form handler and the form configuration is defined in a form type.

The advantages of the FormHandler can best be shown by two examples. The first example we handle a form the "traditional way" (like how it's documented in the Symfony2 documentation). The second example we refactor the first example to a FormHandler implementation. Both examples will use the MyForm form type.

MyFormType.php

<?php

namespace MyBundle\Form;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;

public class MyFormType extends AbstractType
{
   public function buildForm(FormBuilderInterface $builder, array $options)
   {
       $builder->add('title', 'text', []);
   }
}

Example: The traditional way

MyController.php

<?php

namespace MyBundle\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
use MyBundle\Form\MyFormType;

public class MyController extends Controller
{
   public function formAction(Request $request)
   {
        $form = $this->createForm(new MyFormType());
        
        // Check if the request is a POST or GET method
        if ($request->isMethod('POST')) {
            // Handle the request by the form component
            $form->handleRequest($request);
            // Check if the provided data is valid
            if ($form->isValid()) {
                // Business logic to handle the data
                
                // Redirect the user to the home page
                return $this->redirectToRoute('homepage');
            }
        }
   
        return $this->render('form.html.twig', [
            'form' => $form->createView()
        ]);
   }
}

As you probably already noticed, the controller is fat and unit testing will be hard. Now we will refactor this controller to use the FormHandler library.

Example: Refactor and use the FormHandler library instead

MyController.php

<?php

namespace MyBundle\Controller;

use Symfony\Component\HttpFoundation\Request;
use MyBundle\Form\MyFormType;

public class MyController extends Controller
{
   public function formAction(Request $request)
   {
        $formHandler = new PersistEntityFormHandler($this->get('form.factory'), new MyFormType());

        // Check if the request is a POST or GET method
        if ($request->isMethod('POST')) {
            try {
                $formHandler->process($formHandler->form(), $request);

                // Redirect the user to the home page
                return $this->redirectToRoute('homepage');
            } catch (ValidationException $e) {
                // Do something ... or not (and render the page again)
            }
        }

        return $this->render('AppBundle:Schedule:index.html.twig', [
            'form' => $formHandler->form()->createView()
        ]);
   }
}

MyFormHandler.php

<?php

namespace MyBundle\Form\Handler;

use Piwi\Form\Handler\AbstractFormHandler;

public class MyFormHandler extends AbstractFormHandler
{
   protected function postProcess(FormInterface $form, Request $request)
   {
       // The business logic
   }
}

As you can see, all the business logic is moved to the FormHandler instead of in the controller. Now it's possible to unit test your controller and business logic seperatly from each other which is more simple!

If you are using the full-stack Symfony2 framework it is a good practice to register your form handlers as services like this (YAML):

my_form_type:
    class: MyBundle\Form\MyFormType

my_form_type_handler:
    class: MyBundle\Form\Handler\MyFormHandler
    arguments:
        - @form.factory
        - @my_form_type

You can find the FormHandler library on Packagist and GitHub.