Задать вопрос
@fattan
программист

Как в symfony3 сделать form-types заменяемым?

Суть задачи:
Есть документ. У документа есть согласующие. Много разных согласующих. Для примера возьмем 2х: куратор и инициатор. При создании документа пользователь выбирает куратора из списка юзеров с ролью "ROLE_CURATOR", и выбирает инициатора из списка юзеров с ролью инициатор.

Согласующие это экземпляр сущности Approver. схема entity:

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 связан с документом двунаправленной связью.
У Approver-а есть роль (инициатор, куратор, юрист...), заданный пользователь (user).

Approver рисуется в форме с помощью 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()
        ]);
    }


В action задаются 2 согласующих с ролями ROLE_CURATOR и ROLE_INITIATOR.
Форма документа:
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,
        ));
    }
}


В 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,
        ));
    }
}


Как видите, в 'query_builder идёт фильтрация - выводятся только юзеры с ролью куратор (для выбора куратора документа). в графе 'label' указано 'Согласующий'.

Хочется, чтобы при задании в контроллере согласующего с ролью ROLE_CURATOR:
$sSignatureCur = new UserApprover();
        $sSignatureCur->setRole($em->getRepository(\AppBundle\Entity\Role::class)
          ->findOneBy(['name' => 'ROLE_CURATOR']));
        $doc->getApprovers()->add($sSignatureCur);

В form-type задавался 'label' ='Куратор' и в 'query_builder' $signers($er, 'ROLE_CURATOR');

А при задании в контроллере согласующего с ролью ROLE_INITIATOR:
В form-type задавался 'label' ='Инициатор' и в 'query_builder' $signers($er, 'ROLE_INITIATOR');

Есть ли решение данной задачи?
  • Вопрос задан
  • 454 просмотра
Подписаться 1 Средний Комментировать
Решения вопроса 1
BoShurik
@BoShurik Куратор тега Symfony
Symfony developer
Чтобы получить данные в форме надо использовать 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)
            ;
        });
    }
Ответ написан
Комментировать
Пригласить эксперта
Ответы на вопрос 2
Austin_Powers
@Austin_Powers
Web developer (Symfony, Go, Vue.js)
Как вариант:
1) Регистрируете форму как сервис
app.form.type.emergency:
        class: AppBundle\Form\Type\ApproverType
        arguments: 
            - "@service_container"
        tags:
            - { name: form.type }

2) В конструктор формы передаете
Symfony\Component\DependencyInjection\ContainerInterface


3) Определяете в конструкторе текущего пользователя:
/**
     * @param ContainerInterface $container
     */
    public function __construct(ContainerInterface $container)
    {
        $this->user = $container->get('security.token_storage')->getToken()->getUser();
    }


4) Вытаскиваете роль и делаете все что нужно.
Ответ написан
@fattan Автор вопроса
программист
Решил задачу посредством комментариев BoShurik
Выкладываю код получившегося результата.
Визуально форма выглядит так:
4AklO3OTvWQ8rq.png

Action создания нового документа:
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 - основная форма:
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 - вложенная форма выбора согласующих:
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,
        ));
    }
}


Получается, слушатель $listener отрабатывает после загрузки из модели данных в форму ApproverType (событие FormEvents::PRE_SET_DATA):

set_data_flow.png

Слушатель $listener добавляет один единственно нужный select в форму ( $form->add('user', EntityType::class)). Из данных модели ($approver = $event->getData()), выбирается ('label' => $approver->getRole()->getTitle()) информация о роли (эта информация была занесена ранее в контроллере ($userApproverCurator->setRole($roleCurator);))
Ответ написан
Комментировать
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы