Ervin Šabić
Ervin Šabić

Reputation: 125

Accessing User Entity From Form Type in Symfony

I'm trying to build multi-tenancy support in Symfony, doing this I created a group_id column in my User, on top of that, all entities also have this same column. That way users only have access to their group's data.

I've managed to cut through this whole thing like butter with data access and display, but then came the challenge of EntityTypes for the Symfony Forms.

The question is basically, how do I make the EntityType display only the data that that particular group has inputted. Sorting it by the group_id that both the user and contact has. Whats the best way to maybe pass this in so the user only has access to their data?

<?php 

namespace ContactBundle\Form; 

use ContactBundle\Entity\Contact; 
use ContactBundle\Entity\Organization;
use Symfony\Component\Form\AbstractType; 
use Symfony\Component\Form\FormBuilderInterface; 
use Symfony\Component\OptionsResolver\OptionsResolver; 
use Symfony\Component\Form\Extension\Core\Type\TextType; 
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType; 

class ContactType extends AbstractType 
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder 
        ->add('first_name', TextType::Class, [
            'attr' => ['class'=>'u-full-width'],
            'label' => 'First Name',])
        ->add('last_name', TextType::Class, [
            'attr' => ['class'=>'u-full-width'],
            'label'=>'Last Name',
        ])
        ->add('email', TextType::Class, [
            'attr' => ['class'=>'u-full-width'],
            'label'=>'Email Address'
        ])
        ->add('organization_id', EntityType::Class, [
            'attr' => ['class'=>'u-full-width'],
            'required' => false,
            'class'=>'ContactBundle:Organization',
            'choice_label'=>'name',
            'choice_value'=>'id',
            'label'=>'Organization'
        ])
        ->add('phone', TextType::Class, [
            'attr' => ['class'=>'u-full-width'],
            'label'=>'Phone',
        ])
        ->add('role', TextType::Class, [
            'attr' => ['class'=>'u-full-width'],
            'label'=>'Role',
        ])
        ->add('submit', SubmitType::class, [
            'label'=>'Submit', 
            'attr' => [
                'class'=>'button-primary',
                'style'=>'margin-top:30px;',]]);
    }

    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults(['data_class'=>Contact::class,]);
    }
}
?>

It is worth noting that I am using FOSUserBundle.

Upvotes: 1

Views: 3781

Answers (4)

HelpNeeder
HelpNeeder

Reputation: 6480

You could do this in few different ways, one was to use combination of JavaScript and EntityType.

Basically just load all the entities and hide unwanted entities with JS depending on the previous list's selection. You can use data attribute to specify things needed to make that work.

Upvotes: 0

Ervin Šabić
Ervin Šabić

Reputation: 125

I got it!

So what I ended up doing was getting rid of the whole idea of using a EntityType on the form side. I then called the data from the controller and passed it in as an option. Like so:

$contactManager = $this->get('contact.contact_manager');
$contact = new Contact($contactManager->nextId($group = $this->getUser()->getGroupId()), $group);
$form = $this->createForm(ContactType::class, $contact, ['organizations' => $this->get('contact.organization_manager')->findDataCollection($this->getUser()->getGroupId())]);
$form->handleRequest($request);

Turned my EntityType into a choice type and passed the array of organizations into the 'choices' field as an option. Kept everything else the same like so:

    ->add('organization_id', ChoiceType::Class, [
        'attr' => ['class'=>'u-full-width'],
        'required' => false,
        'choices'=> $options['organizations'],
        'choice_label'=>'name',
        'choice_value'=>'id',
        'label'=>'Organization'
    ])

Then, of course, set it within the options so it can expect a variable.

$resolver->setDefaults(['data_class'=>Contact::class, 'organizations' => null]);

I appreciate all of the help and ideas!! :)

Upvotes: 0

JimL
JimL

Reputation: 2541

In Symfony it's very easy to inject whatever you need where you need it.

// ...
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;

class ContactType extends AbstractType
{
    private $user;

    public function __construct(TokenStorageInterface $tokenStorage)
    {
        $this->user = $tokenStorage->getToken()->getUser();
    }

    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            // ...
            ->add('organization_id', EntityType::Class, [
                'attr' => ['class'=>'u-full-width'],
                'required' => false,
                'class'=>'ContactBundle:Organization',
                'query_builder' => function (EntityRepository $er) {
                    return $er->createQueryBuilder('u')
                        ->where('u.group', '?0')
                        ->setParameters([$this->user->getGroup()]);
                },
                'choice_label'=>'name',
                'choice_value'=>'id',
                'label'=>'Organization'
            ])
            // ...

If you are not using autowire and autoconfigure (Symfony 3.3+), register your form as a service manually and tag it with form.type:

# config/services.yaml
services:
    AppBundle\Form\ContactType:
        arguments: ['@security.token_storage']
        tags: [form.type]

Related reads

https://symfony.com/doc/current/form/form_dependencies.html#define-your-form-as-a-service

https://symfony.com/doc/current/reference/forms/types/entity.html#using-a-custom-query-for-the-entities

Upvotes: 5

Denis Alimov
Denis Alimov

Reputation: 2891

In controller when creating form

$form = $this->createForm(ContactType::class, $contact, ['group_id' => $groupId]);

In form configureOptions method

$resolver->setDefaults(['data_class'=>Contact::class, 'group_id' => null]);

In form buildForm you can get group_id by

$options['group_id']

Upvotes: 0

Related Questions