Protéger un champ de formulaire Symfony2 avec un droit

Cet article a été publié depuis plus de 6 mois, cela signifie que le contenu peut ne plus être d'actualité.

Il arrive fréquemment que l'on souhaite restreindre l'accès à certains champs de nos formulaires avec un (ou plusieurs) droit(s) utilisateur. Pour cela, une majorité de développeurs passera un paramètre dans le tableau d'options reçu par la fonction buildForm obligeant alors à modifier le code du formulaire pour traiter l'information reçue. Nous allons voir une alternative beaucoup plus élégante.

Pour cela nous allons créer une extension de type de formulaire qui se chargera de la vérification des droits et affichera ou non auprès de l' utilisateur de notre application.

Prenons pour exemple un formulaire permettant de créer un billet de blog :

<?php

namespace JDecool\Bundle\ForumBundle\Form;

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

class ThreadType extends AbstractType
{
     /**
     * @param FormBuilderInterface $builder
     * @param array $options
     */
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('title')
            ->add('content', 'textarea')
            ->add('solved', 'checkbox', [
                'required'   => false,
            ])
        ;
    }

    /**
     * @param OptionsResolverInterface $resolver
     */
    public function setDefaultOptions(OptionsResolverInterface $resolver)
    {
        $resolver->setDefaults(array(
            'data_class' => 'JDecool\Bundle\ForumBundle\Entity\Thread'
        ));
    }

    /**
     * @return string
     */
    public function getName()
    {
        return 'forum_thread';
    }
}

Maintenant que notre formulaire est défini, nous allons restreindre l'affichage du champ solved uniquement au utilisateurs ayant le rôle ROLE_ADMIN. Pour cela nous allons simplement ajouter une nouvelle propriété au champ correspondant.

public function buildForm(FormBuilderInterface $builder, array $options)
{
    $builder
        ->add('title')
        ->add('content', 'textarea')
        ->add('solved', 'checkbox', [
            'required'   => false,
            'is_granted' => 'ROLE_ADMIN',
        ])
    ;
}

Pour que ce paramètre soit interprété, nous allons créer notre extension de type formulaire en lui injectant le SecurityContext Symfony afin de vérifier les droits de l'utilisateur connecté.

<?php

namespace JDecool\Bundle\ForumBundle\Form\Extension;

use Symfony\Component\Form\AbstractTypeExtension;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
use Symfony\Component\Security\Core\SecurityContextInterface;

class SecurityTypeExtension extends AbstractTypeExtension
{
    /**
     * The security context
     * @var SecurityContextInterface
     */
    private $securityContext;

    /**
     * Object constructor
     */
    public function __construct(SecurityContextInterface $securityContext)
    {
        $this->securityContext = $securityContext;
    }

    /**
     * {@inheritdoc}
     */
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $grant = $options['is_granted'];
        if (null === $grant || $this->securityContext->isGranted($grant)) {
            return;
        }

        $builder->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event) {
            $form = $event->getForm();
            if ($form->isRoot()) {
                return;
            }

            $form->getParent()->remove($form->getName());
        });
    }

    /**
     * {@inheritdoc}
     */
    public function setDefaultOptions(OptionsResolverInterface $resolver)
    {
        $resolver->setDefaults(array('is_granted' => null));
    }

    /**
     * {@inheritdoc}
     */
    public function getExtendedType()
    {
        return 'form';
    }

}

Il ne reste plus qu'à enregistrer notre extension en tant que service :

services:
    jdecool_forum_bundle.security_type_extension:
        class: JDecool\Bundle\ForumBundle\Form\Extension\SecurityTypeExtension
        arguments:
            - @security.context
        tags:
            - { name: form.type_extension, alias: form }

Et voilà, il est maintenant possible de restreindre n'importe quel champ de formulaire avec un droit utilisateur sans avoir besoin de donner en paramètre l'utilisateur connecté ou à manipuler ce dernier dans notre classe de formulaire.


Mise à jour du 10/09/2014: Suite à de nombreuses réactions sur Twitter, je tiens à rendre à Ceasar ce qui appartient à Ceasar. Je ne suis pas l'auteur de ce code. Ce code a été trouvé dans un projet sur lequel je travaillais et que j'ai souhaité partager. Après des discussions, l'auteur est Alexandre SALOME, consultant chez SensioLabs qui avait donné cette astuce lors d'une présentation à un sfPot à Paris.