Creating decision making Forms

02. August 2016 Form, Symfony 1

Every web application you make, I make, your collegae makes, is in need of some input. For that input we use forms. What else.

But we often get the question that parts of the form should be altered for different users with different access roles. Now, you can go and do this two different ways

  1. Creating a different class holding the form for each different role
  2. Creating the full form and adding the elements based on a decision.

The first option is simple. You create one or multiple classes for each form holding the exact information you wish to display to the user. You can imagine that if every form in your application is built this way, you will soon have a ton of form class, which makes this part of your application not maintainable to say the least. Also, bear in mind that your controller will have to take the hit of implementing this logic each time you wish to use this form. Now, you ask, how will this look in my application. Well…

// Structure
Bundle
|
-- Form
  |
  -- Type
    |
	-- AdminUserFormType.php
	-- PrivilegedUserFormType.php
	-- UserFormType.php
// Controller
...
use Bundle\Form\Type\AdminUserFormType;
use Bundle\Form\Type\PrivilegedUserFormType;
use Bundle\Form\Type\UserFormType;
...
public function formAction()
{
	$authoriztion = $this->get('security.authoriztion_checker');
	if ($authoriztion->isGranted('ROLE_ADMIN')) {
		$formType = AdminUserFormType::class;
	} elseif ($authoriztion->isGranted('ROLE_PRIVILEGED')) {
		$formType = PrivilegedUserFormType::class;
	} else {
		$formType = UserFormType::class;
	}
	
	$object = new SomeObject();
	$form = $this->createForm($formType, $object);
}

As you see,  it is far from clean for your controller and giving it an unnecessary complexity. Again, keep in mind that when the complexity of your application grows (more roles, more forms), complexity growth to your controllers are a given.

The second thought was to make decisions in the form class. In the previous example I demonstrated making a decision based on the role. The Authorization Checker class I used can be injected in to constructor of the form class. This would look as follows:

<?php
// Form class

namespace App\Bundle\Form\Type;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Security\Core\Authorization\AuthorizationChecker;

class UserType extends AbstractType
{
	protected $authorization;
	
	public function __construct(AuthorizationChecker $authorizationChecker)
	{
		$this->authorization = $authorizationChecker;
	}
	
	public function buildForm(FormBuilderInterface $builder, array $options)
        {
		$builder->add('someField', TextType::class)
			->add('someOtherField', TextType::class);
			
		if ($this->authorization->isGranted("ROLE_ADMIN")) {
			$builder->add('someProtectedField', TextType::class);
		}
	}
}

As you can see, I added some fields which are common to all users and one field which is visible to an Admin user. Initiating this form in the controller would be like:

<?php
// Controller
...
use App\Bundle\Form\Type\UserType;
...

public function formAction()
{
	$user = $this->getUser();
	$form = $this->createForm(UserType::class, $user);
}

But, wait, what!. How is the Authorization Checker injected to the form type?

Prior to Symfony 3 you could create forms using the object instead of the FQCN.

$form = $this->createForm(new UserType($this->get('security.authorization_checker')), $user);

But since Symfony 2.8 this started throwing deprecation messages, because of these changes to the form creation in Symfony 3. So, how should we handle this? The keyword is services. These services run in the background of the Symfony Framework and can be requested at any moment when you would need it. A service in Symfony can be defined as follows:

A piece of functionality in your application which has dependencies of it’s own in order to function.

The call to the authorization_checker you see in the example above is also a service.

The service can be created by adding the following to your services.yml file (which can be found in src/App/Bundle/Resources/config/ for example):

services:
    app_bundle.user_form_type: # The name of your service
        class: App\Bundle\Form\Type\UserType # The class which has the dependency
        arguments: [ "@security.authorization_checker" ] # Add the dependency
        tags: # This type of service has to be defined as a Form Type
            - {name: form.type }

Now, when creating the form, the dependency gets injected into the constructor.

Next time I will be discussing how to handle decision making in your form when you have a custom object, which can’t be defined as a service.


1 thought on “Creating decision making Forms”

Leave a Reply

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