Decision making forms: Part 2

11. April 2017 Form, Symfony 0

Last time, and yes, it has been a while, I talked about Creating decision making forms. In this post, I will continue where we left off.

Most of the time you won’t need any dependencies when creating your form. But when you do, it will likely be a dependency which is available as a Symfony service object. As we have already discussed this in the previous post, I won’t bother you with a recap.

Now, there may come a time when you are in need of an object from which you need logic but it is unavailable as a service. Sadly, this means it can’t be passed into the constructor (yes, yes, I know, I’m still bothering you with a recap).

Let’s look at a code example

<?php

namespace AppBundle\Form\Type;

use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\EmailType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Security\Core\User\User;

class CompanyType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('name', TextType::class)
            ->add('phone', TextType::class)
            ->add('email', EmailType::class)
            ->add('manager', EntityType::class, [
                'class' => User::class,
            ]);
    }
}

This form represents a Company entity which contains some properties. The manager in this case is the property I want to zoom in on. In the current setting, all users in the User entity are loaded in the form. Imagine you are creating or modifying this company for a certain department in which case you only wish to see the users of that department.

So, consider this controller:

<?php

namespace AppBundle\Controller;

...

class AppController extends Controller
{
    /**
     * @Route("/create/{department}")
     * @Template()
     * @param Request $request
     * @param Department $department
     * @return array
     */
    public function createAction(Request $request, Department $department)
    {
        $company = new Company();
        $form = $this->createForm(CompanyType::class, $company);
        $populator = new CompanyPopulator();
        $form = $populator->populate($form, $department);
        $form->handleRequest($request);

        if ($form->isSubmitted() && $form->isValid()) {
            // Do your thing
        }

        return [
            'form' => $form->createView()
        ];
    }
}

And the populator:

<?php

namespace AppBundle\Form\Populator;

use AppBundle\Entity\Department;
use Doctrine\ORM\EntityRepository;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\Form;
use Symfony\Component\Security\Core\User\User;

class CompanyPopulator
{
    public function populate(Form $form, Department $department)
    {
        $form->add('manager', EntityType::class, [
            'class' => User::class,
            'query_builder' => function(EntityRepository $repository) use ($department) {
                return $repository->createQueryBuilder('u')
                    ->where('u.department = :department')
                    ->setParameter('department', $department);
            }
        ]);

        return $form;
    }
}

In the controller the well-known part occurs. The company object is passed to the createForm method together with the classname of the form type. Request is handled and the form is being checked for submission. Same old, same old.

Now, where this gets interesting is the populator being called for duty. We want to show only the users of the department which we are mutating for. But, as stated, we don’t have access to the department object within the form builder. So by passing the form and the department into a class handling this for you, this issue gets resolved.

The populator redefines the form property ‘manager’ by adding it again. By doing so, the original property created in the builder gets overwritten. As you can see, I added a query_builder option so I can filter out all the users not related to the department. This way, less mistakes are made like selecting a wrong manager.

The populator approach is way to modify the form after it has left the builder with external logic, unrelated to the company or the current user. It simplifies the functionality in the controller and is available to reuse in other parts of your application.


Leave a Reply

Your email address will not be published. Required fields are marked *