A
A
Anton2017-05-10 13:28:47
symfony
Anton, 2017-05-10 13:28:47

How to make form-types replaceable in symfony3?

Essence of the problem:
There is a document. The document has approvals. Lots of different matchers. For example, let's take 2x: the curator and the initiator. When creating a document, the user selects a curator from the list of users with the role "ROLE_CURATOR", and selects an initiator from the list of users with the role of initiator.
Approvers are an instance of the Approver entity. entity schema:

Finance\ExpBundle\Entity\Approver:
    type: entity
    table: exp_approver
    id:
        id:
            type: integer
            nullable: false
            options:
                unsigned: true
            id: true
            generator:
                strategy: IDENTITY
    manyToOne:
        user:
            targetEntity: \AppBundle\Entity\User
            joinColumn:
                name: user_id
                referencedColumnName: id
        role:
            targetEntity: \AppBundle\Entity\Role
            joinColumn:
                name: role_id
                referencedColumnName: id
        document:
            targetEntity: Finance\ExpBundle\Entity\Document
            inversedBy: approvers
            joinColumn:
                name: document_id
                referencedColumnName: id

Approver is associated with the document by a bi-directional relationship.
Approver has a role (initiator, curator, lawyer...), a given user (user).
The Approver is drawn on the form using the CollectionType. DocumentController:createAction:
public function createAction(Request $request)
    {
        $em = $this->getDoctrine()->getManager();

        $doc = new Document();

        $sSignatureCur = new UserApprover();
        $sSignatureCur->setRole($em->getRepository(\AppBundle\Entity\Role::class)
          ->findOneBy(['name' => 'ROLE_CURATOR']));
        $sSignatureCur->setDocument($doc);

        $sSignatureIni = new UserApprover();
        $sSignatureIni->setRole($em->getRepository(\AppBundle\Entity\Role::class)
          ->findOneBy(['name' => 'ROLE_INITIATOR']));
        $sSignatureIni->setDocument($doc);

        $doc->getApprovers()->add($sSignatureIni);
        $doc->getApprovers()->add($sSignatureCur);

        $form = $this->createForm(DocumentType::class, $doc);
        $form->handleRequest($request);

        if ( $form->isSubmitted() && $form->isValid() ) {

            $em = $this->getDoctrine()->getManager();

            foreach ($doc->getApprovers() as $approver){
                $em->persist($approver);
            }

              // ...

            return $this->redirectToRoute('finance_exp_view', ['id' => $doc->getId()]);
        }

        return $this->render('::doc.create.html.twig', [
          'form' => $form->createView()
        ]);
    }

In action, 2 matching with the roles ROLE_CURATOR and ROLE_INITIATOR are set.
Document form:
class DocumentType extends AbstractType
{

    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
          ->add('name', TextType::class, [
            'label' => 'Предмет договора',
          ])
        ->add('approvers', CollectionType::class, array(
          'entry_type' => ApproverType::class
        ));
    }

    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults(array(
          'data_class' => Document::class,
        ));
    }
}

In ApproverType:
class ApproverType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
    // фильтруем юзеров по роли
        $signers = function (EntityRepository $er, $roleName){

            $qb = $er->createQueryBuilder('u')
              ->innerJoin('u.userRoles','r')
              ->where('r.name=?1')
              ->setParameter(1, $roleName)
              ->orderBy('u.lastName', 'ASC')
            ;
            return $qb;
        };

        $builder->add('user', EntityType::class, [
          'class' => 'AppBundle:User',
          'label' => 'Согласующий',
          'choice_label' => function ($user) {
              return $user->getFullName() . $absent;
          },
          'query_builder' => function(EntityRepository $er) use ($signers) {
              return $signers($er, 'ROLE_CURATOR');
          },
          'placeholder' => '--- Выберите ---'
        ]);
    }

    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults(array(
          'data_class' => Approver::class,
        ));
    }
}

As you can see, 'query_builder' is filtering - only users with the curator role are displayed (to select the curator of the document). the 'label' column indicates 'Coordinator'.
I would like that when setting in the controller the ROLE_CURATOR matching with the role:
$sSignatureCur = new UserApprover();
        $sSignatureCur->setRole($em->getRepository(\AppBundle\Entity\Role::class)
          ->findOneBy(['name' => 'ROLE_CURATOR']));
        $doc->getApprovers()->add($sSignatureCur);

The form-type was set to 'label' ='Curator' and the 'query_builder' $signers($er, 'ROLE_CURATOR');
And when setting the ROLE_INITIATOR matching with the role in the controller:
'label' = 'Initiator' was set in form-type and $signers($er, 'ROLE_INITIATOR'); in 'query_builder';
Is there a solution to this problem?

Answer the question

In order to leave comments, you need to log in

3 answer(s)
B
BoShurik, 2017-05-11
@fattan

To get data in a form, use symfony.com/doc/current/form/events.html

public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder->addEventListener(FormEvents::PRE_SET_DATA, function(FormEvent $event){
            if (!$data = $event->getData()) {
                return;
            }
            $form = $event->getForm();
            // Тут можно в зависимо от $data навешивать свои поля
            $form
                ->add('someField', TextType::class)
            ;
        });
    }

O
Oleg, 2017-05-10
@Austin_Powers

As an option:
1) Register the form as a service

app.form.type.emergency:
        class: AppBundle\Form\Type\ApproverType
        arguments: 
            - "@service_container"
        tags:
            - { name: form.type }

2) In the form constructor, pass
3) Define the current user in the constructor:
/**
     * @param ContainerInterface $container
     */
    public function __construct(ContainerInterface $container)
    {
        $this->user = $container->get('security.token_storage')->getToken()->getUser();
    }

4) Pull out the role and do whatever you need.

A
Anton, 2017-05-11
@fattan

I solved the problem with BoShurik's
comments. I'm posting the code of the result.
Visually, the form looks like this:
Action for creating a new document:

public function createAction(Request $request)
    {
        $doc = new Document();

        $roleCurator = $this->getRole('ROLE_FINANCE_CURATOR');
        $userApproverCurator = new UserApprover();
        $userApproverCurator->setRole($roleCurator);
        $userApproverCurator->setDocument($doc);

        $roleInitiator = $this->getRole('ROLE_FINANCE_INITIATOR');
        $userApproverInitiator = new UserApprover();
        $userApproverInitiator->setRole($roleInitiator);
        $userApproverInitiator->setDocument($doc);

        $doc->getApprovers()->add($userApproverInitiator);
        $doc->getApprovers()->add($userApproverCurator);

        $form = $this->createForm(DocumentType::class, $doc);
        $form->handleRequest($request);

        if ( $form->isSubmitted() && $form->isValid() ) {

            $em = $this->getDoctrine()->getManager();

            foreach ($doc->getApprovers() as $approver){
                $em->persist($approver);
            }

      // ... other code

            $em->persist($doc);
            $em->flush();


            return $this->redirectToRoute('finance_exp_view', ['id' => $doc->getId()]);
        }

        return $this->render('::doc.create.html.twig', [
          'form' => $form->createView()
        ]);
    }

DocumentType - main form:
class DocumentType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
          ->add('name', TextType::class, [
            'label' => 'Предмет договора',
          ])
          ->add('price', NumberType::class, [
            'label' => 'Цена',
            'scale' => 2,
            'grouping' => NumberFormatter::GROUPING_USED
          ])

      // ... other fields
        
        ->add('approvers', CollectionType::class, [
          'entry_type' => ApproverType::class
        ]);
    }

    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults(array(
          'data_class' => Document::class,
        ));
    }
}

ApproverType - nested form for selecting approvers:
class ApproverType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $listener = function (FormEvent $event) use($builder) {

            /** @var UserApprover $approver */
            if (!$approver = $event->getData()) {
                return;
            }

            $form = $event->getForm();
            $form->add('user', EntityType::class, [
              'class' => 'AppBundle:User',
              'label' => $approver->getRole()->getTitle(),
              'choice_label' => function ($user) {
                  /** @var User $user */
                  $absent = (!$user->getAbsent()) ? '' : ' (отсутствует)';
                  return $user->getFullName() . $absent;
              },
              'query_builder' => function (EntityRepository $er) use ($approver){
                  $qb = $er->createQueryBuilder('u')
                    ->innerJoin('u.userRoles','r')
                    ->where('r.name=?1')
                    ->setParameter(1, $approver->getRole()->getName())
                    ->orderBy('u.lastName', 'ASC')
                  ;
                  return $qb;
              },
              'placeholder' => '--- Выберите ---'
            ]);

        };

        $builder->addEventListener(FormEvents::PRE_SET_DATA, $listener);
    }

    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults(array(
          'data_class' => Approver::class,
        ));
    }
}

It turns out that the $listener listener works after loading from the data model into the ApproverType form (FormEvents::PRE_SET_DATA event):
The $listener listener adds the only necessary select to the form ( $form->add('user', EntityType::class)). From the model data ($approver = $event->getData()), select ('label' => $approver->getRole()->getTitle()) information about the role (this information was entered earlier in the controller ($userApproverCurator ->setRole($roleCurator);))

Didn't find what you were looking for?

Ask your question

Ask a Question

731 491 924 answers to any question