关于symfony doc on form events,我试图在用户更改其他字段时动态激活/停用或设置多个字段的值...
FormType
<?php
namespace AppBundle\Form;
use AppBundle\Entity\ConsumptionMode;
use AppBundle\Entity\Operation;
use AppBundle\Entity\PaymentMode;
use AppBundle\Entity\Service;
use AppBundle\Entity\User;
use AppBundle\Repository\ServiceRepository;
use AppBundle\Repository\UserRepository;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\Extension\Core\Type\CollectionType;
use Symfony\Component\Form\Extension\Core\Type\DateType;
use Symfony\Component\Form\Extension\Core\Type\IntegerType;
use Symfony\Component\Form\Extension\Core\Type\MoneyType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
/**
* Class OperationType
* @package AppBundle\Form
*/
class OperationType extends AbstractType
{
/**
* @param FormBuilderInterface $builder
* @param array $options
* @throws \Symfony\Component\Form\Exception\InvalidArgumentException
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
//default displayed form inputs
//the user must select a service to enable other fields
//the quantity and the dates will not change on the service selection
//the user list, the price and the billable fields will change on service selection
$builder
//HERE IS THE FIRST FIELD I WANT TO LISTEN TO
->add('service', EntityType::class, [
'class' => Service::class,
'choice_label' => 'name',
'placeholder' => '-- Choose a service --',
'query_builder' => function(ServiceRepository $sr) {
return $sr->createQueryBuilder('s')
->where('s.enabled = true');
}
])
->add('quantity', IntegerType::class)
->add('consumptionMode', ChoiceType::class, [
'choices' => ConsumptionMode::AVAILABLE_MODES
])
//HERE IS THE SECOND FIELD I WANT TO LISTEN TO
// ->add('paymentMode', ChoiceType::class, [
// 'choices' => PaymentMode::AVAILABLE_MODES,
// 'required' => false
// ])
->add('dateStart', DateType::class, ['required' => false])
->add('dateEnd', DateType::class, ['required' => false])
->add('dateEndPostponed', DateType::class, ['required' => false])
//allow dynamic add / delete of sub-operations
->add('children', CollectionType::class, [
'label' => 'Sub-Operations',
'entry_type' => SubOperationType::class,
'allow_add' => true,
'allow_delete' => true,
'by_reference' => false,
'prototype_name' => '__sub_operation_index__'
]);
//add listener to change the default values when loading the form
$builder->addEventListener(FormEvents::PRE_SET_DATA, [$this, 'onPresetData']);
//add listener to change the default values when the user select a service
$builder->get('service')->addEventListener(FormEvents::POST_SUBMIT, [$this, 'onPostSubmit']);
//HERE IS THE SECOND FIELD LISTENER
// //add listener to change the enable extra qtity and price values when the user select a payment mode
// $builder->get('paymentMode')->addEventListener(FormEvents::POST_SUBMIT, [$this, 'onPostSubmit']);
}
/**
* @param FormEvent $event
* @throws \Symfony\Component\Form\Exception\AlreadySubmittedException
* @throws \Symfony\Component\Form\Exception\LogicException
* @throws \Symfony\Component\Form\Exception\UnexpectedTypeException
* @throws \OutOfBoundsException
*/
public function onPresetData(FormEvent $event)
{
//get the closure that will add and initialise the user/price/billable fields
$formModifier = $this->getFormModifier();
//Note: As we use this form as a "prototype" in Package & RFOP,
//The event->getData will return null on the prototype creation (i.e onPreSetData event)
$service = $event->getData() !== null ? $event->getData()->getService() : null;
//call the closure with the event
$formModifier($event->getForm(), $service);
}
/**
* @param FormEvent $event
* @throws \Symfony\Component\Form\Exception\AlreadySubmittedException
* @throws \Symfony\Component\Form\Exception\LogicException
* @throws \Symfony\Component\Form\Exception\UnexpectedTypeException
* @throws \OutOfBoundsException
*/
public function onPostSubmit(FormEvent $event)
{
//get the closure that will add and initialise the user/price/billable/... fields
$formModifier = $this->getFormModifier();
// It's important here to fetch $event->getForm()->getData(), as
// $event->getData() will get the client data (that is, the ID)
$service = $event->getForm()->getData();
// since we've added the listener to the child, we'll have to pass on
// the parent to the callback functions!
$formModifier($event, $event->getForm()->getParent(), $service);
}
/**
* @param OptionsResolver $resolver
* @throws \Symfony\Component\OptionsResolver\Exception\AccessException
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => Operation::class
]);
}
/**
* Get the managers linked to the Service::defaultAllocationTeam
*
* @param Service $service
* @return User[]
*/
private function getDefaultTeamUserMangers(Service $service)
{
$users = [];
$userTeams = $service->getDefaultAllocationTeam()->getUserTeams();
foreach($userTeams as $userTeam) {
$user = $userTeam->getUser();
//TODO Maybe use the authorizationChecker to do this ?
//FIXME declare Roles constants in User entity
//only add the managers
if ($user->hasRole('ROLE_MANAGER')) {
//add user
$users[] = $user;
}
}
return $users;
}
/**s
* add the users field in the form
* preset the data using the selected service
*
* @param FormInterface $form
* @param Service|null $service
* @throws \Symfony\Component\Form\Exception\AlreadySubmittedException
* @throws \Symfony\Component\Form\Exception\LogicException
* @throws \Symfony\Component\Form\Exception\UnexpectedTypeException
* @throws \OutOfBoundsException
*/
private function addUserField(FormInterface $form, Service $service = null)
{
//default user field options
$options = [
'multiple' => true,
'class' => User::class,
'disabled' => true,//disable the field by default
'choice_label' => 'email',
'query_builder' => function (UserRepository $ur) {
return $ur->createQueryBuilder('u')
->where('u.enabled = true')
->orderBy('u.email', 'ASC');
}
];
//enable user field when the user selected a service
if ($service !== null) {
//get the preferred users (managers) for the service (using the defaultAllocationTeam service field)
$defaultTeamUsers = $this->getDefaultTeamUserMangers($service);
//enable the user field and set preferred choices
$options = array_merge($options, [
'disabled' => false,
'preferred_choices' => function($user) use ($defaultTeamUsers) {
return in_array($user, $defaultTeamUsers, true);
}
]);
//TODO preset users
}
//create the users field
$form->add('users', EntityType::class, $options);
}
/**
* Add price field
* Preset the price value using the service defaultUnitPrice field
*
* @param FormInterface $form
* @param Service|null $service
* @throws \Symfony\Component\Form\Exception\AlreadySubmittedException
* @throws \Symfony\Component\Form\Exception\LogicException
* @throws \Symfony\Component\Form\Exception\UnexpectedTypeException
* @throws \OutOfBoundsException
*/
private function addPriceField(FormInterface $form, Service $service = null)
{
$options = [
'currency' => 'EUR',
'required' => false,
'disabled' => true
];
//enable the price field
if ($service !== null) {
$options = array_merge($options, [
'disabled' => false,
//set the price in the form
'empty_data' => $service->getDefaultUnitPrice()
]);
}
//create the form field
$form->add('unitPrice', MoneyType::class, $options);
}
/**
* Add billable field
* Preset the isBillable value using the Service::isBillable function
*
* @param FormInterface $form
* @param Service|null $service
* @throws \Symfony\Component\Form\Exception\AlreadySubmittedException
* @throws \Symfony\Component\Form\Exception\LogicException
* @throws \Symfony\Component\Form\Exception\UnexpectedTypeException
* @throws \OutOfBoundsException
*/
private function addBillableField(FormInterface $form, Service $service = null)
{
$options = [
'label' => 'Billable ?',
'required' => false,
'disabled' => true
];
if ($service !== null) {
//check billable checkbox only if isBillable is true
if ($service->isBillable()) {
//only add the empty_data option if the service is billable
//note: if we add 'empty_data' with false, the checkbox will still be checked
$options['empty_data'] = true;
}
//enable the billable field
$options = array_merge($options, [
'disabled' => false,
]);
}
//create the form field
$form->add('billable', CheckboxType::class, $options);
}
/**
* Add ExtraAllowedQuantity field
* Activate this field only if the user set PAY_AS_YOU paymentMode
*
* @param FormInterface $form
* @param null|string $paymentMode
* @throws \Symfony\Component\Form\Exception\AlreadySubmittedException
* @throws \Symfony\Component\Form\Exception\LogicException
* @throws \Symfony\Component\Form\Exception\UnexpectedTypeException
*/
private function addExtraAllowedQuantityField(FormInterface $form, string $paymentMode = null)
{
$options = [
'required' => false,
'disabled' => true
];
//enable the extraAllowedQuantity field only when PAY_AS_YOU_GO is selected
if ($paymentMode === PaymentMode::PAY_AS_YOU_GO) {
$options['required'] = true;
$options['disabled'] = false;
}
$form->add('extraAllowedQuantity', IntegerType::class, $options);
}
/**
* Add ExtraPrice field
* Activate this field only if the user selected PAY_AS_YOU_GO paymentMode
*
* @param FormInterface $form
* @param string|null $paymentMode
* @throws \Symfony\Component\Form\Exception\AlreadySubmittedException
* @throws \Symfony\Component\Form\Exception\LogicException
* @throws \Symfony\Component\Form\Exception\UnexpectedTypeException
*/
private function addExtraPriceField(FormInterface $form, string $paymentMode = null)
{
$options = [
'currency' => 'EUR',
'required' => false,
'disabled' => true
];
//enable the extraAllowedQuantity field only when PAY_AS_YOU_GO is selected
if ($paymentMode === PaymentMode::PAY_AS_YOU_GO) {
$options['required'] = true;
$options['disabled'] = false;
}
$form->add('extraPrice', MoneyType::class, $options);
}
/**
* @return \Closure
* @throws \OutOfBoundsException
* @throws \Symfony\Component\Form\Exception\UnexpectedTypeException
* @throws \Symfony\Component\Form\Exception\LogicException
* @throws \Symfony\Component\Form\Exception\AlreadySubmittedException
*/
private function getFormModifier()
{
return function (FormInterface $form, Service $data = null) {
//Note: As we use this form as a "prototype" in Package & RFOP,
//The event->getData will return null on the prototype creation (i.e onPreSetData event)
//add the user field to the form
$this->addUserField($form, $data);
//add the billable field to the form
$this->addBillableField($form, $data);
//add the price field to the form
$this->addPriceField($form, $data);
//HERE IS WHAT I WANT TO ADD FOR THE PAYMENT_MODE FIELD
// //add the extra price field to the form
// $this->addExtraPriceField($form, $data);
// //add the extra quantity field to the form
// $this->addExtraAllowedQuantityField($form, $data);
};
}
}
此时,service
字段上的监听器完全按预期运行(表单加载了users / unitPrice
字段已禁用 ...当用户选择了service
,users
和unitPrice
字段已启用并使用服务默认信息进行设置。)
我需要做的是在paymentMode
字段上添加另一个侦听器。
首先,表单会将extraAllowedQtity / extraPrice
加载为禁用,不需要;当用户选择PAY_AS_YOU_GO
paymentMode
时,系统会启用并需要此字段。
Behavior on loading /add page(请参阅评论,了解选择服务时的行为)
所以pre_set_data监听器将初始化所有字段,据我所知,post_submit将处理表单字段的修改......
事实是,当我提交服务时,会触发paymentMode postSubmit侦听器......
我很确定我错过了关于form_event系统的一些内容,但我不知道是什么。
注意:我知道我应该从formType中获取监听器,但是在成功完成FormType中的工作之后我会这样做。
答案 0 :(得分:0)
我设法做了我想做的事,如果它可以提供帮助,这就是代码:
<?php
namespace AppBundle\Form;
use AppBundle\Entity\ConsumptionMode;
use AppBundle\Entity\Operation;
use AppBundle\Entity\PaymentMode;
use AppBundle\Entity\Service;
use AppBundle\Entity\User;
use AppBundle\Repository\ServiceRepository;
use AppBundle\Repository\UserRepository;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\Extension\Core\Type\CollectionType;
use Symfony\Component\Form\Extension\Core\Type\DateType;
use Symfony\Component\Form\Extension\Core\Type\IntegerType;
use Symfony\Component\Form\Extension\Core\Type\MoneyType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
/**
* Class OperationType
* @package AppBundle\Form
*/
class OperationType extends AbstractType
{
/**
* @param FormBuilderInterface $builder
* @param array $options
* @throws \Symfony\Component\Form\Exception\InvalidArgumentException
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
//default displayed form inputs
//the user must select a service to enable other fields
//the quantity and the dates will not change on the service selection
//the user list, the price and the billable fields will change on service selection
$builder
->add('service', EntityType::class, [
'required' => true,
'class' => Service::class,
'choice_label' => 'name',
'placeholder' => '-- Choose a service --',
'query_builder' => function(ServiceRepository $sr) {
return $sr->createQueryBuilder('s')
->where('s.enabled = true');
}
])
->add('billable', CheckboxType::class, [
'label' => 'Billable ?',
'required' => false,
'disabled' => true
])
->add('unitPrice', MoneyType::class, [
'currency' => 'EUR',
'required' => false,
'disabled' => true
])
->add('quantity', IntegerType::class, ['required' => true])
->add('consumptionMode', ChoiceType::class, [
'placeholder' => '-- Choose a consumption mode --',
'required' => false,
'choices' => ConsumptionMode::AVAILABLE_MODES
])
->add('paymentMode', ChoiceType::class, [
'choices' => PaymentMode::AVAILABLE_MODES,
'required' => false,
'placeholder' => '-- Choose a payment mode --'
])
->add('extraAllowedQuantity', IntegerType::class, [
'required' => false,
'disabled' => true
])
->add('extraPrice', MoneyType::class, [
'currency' => 'EUR',
'required' => false,
'disabled' => true
])
->add('dateStart', DateType::class, ['required' => false])
->add('dateEnd', DateType::class, ['required' => false])
->add('dateEndPostponed', DateType::class, ['required' => false])
->add('users', EntityType::class, [
'multiple' => true,
'class' => User::class,
'disabled' => true,//disable the field by default
'choice_label' => 'email',
'query_builder' => function (UserRepository $ur) {
return $ur->createQueryBuilder('u')
->where('u.enabled = true')
->orderBy('u.email', 'ASC');
}
])
//allow dynamic add / delete of sub-operations
->add('children', CollectionType::class, [
'label' => 'Sub-Operations',
'entry_type' => SubOperationType::class,
'allow_add' => true,
'allow_delete' => true,
'by_reference' => false,
'prototype_name' => '__sub_operation_index__'
]);
//add listener to change the default values when loading the form
$builder->addEventListener(FormEvents::PRE_SET_DATA, [$this, 'onPresetData']);
//add listener to change the default values when the user select a service
$builder->get('service')->addEventListener(FormEvents::POST_SUBMIT, [$this, 'onPostSubmitService']);
//add listener to change the enable extra qtity and price values when the user select a payment mode
$builder->get('paymentMode')->addEventListener(FormEvents::POST_SUBMIT, [$this, 'onPostSubmitPaymentMode']);
}
/**
* @param FormEvent $event
* @throws \Symfony\Component\Form\Exception\AlreadySubmittedException
* @throws \Symfony\Component\Form\Exception\LogicException
* @throws \Symfony\Component\Form\Exception\UnexpectedTypeException
* @throws \OutOfBoundsException
*/
public function onPresetData(FormEvent $event)
{
$formModifier = $this->getFormModifier();
//Note: As we use this form as a "prototype" in Package & RFOP,
//The event->getData will return null on the prototype creation (i.e onPreSetData event)
$service = $event->getData() === null ? null : $event->getData()->getService();
$paymentMode = $event->getData() === null ? null : $event->getData()->getPaymentMode();
//call the closure with the event
$formModifier($event->getForm(), $service, $paymentMode);
}
/**
* @param FormEvent $event
* @throws \Symfony\Component\Form\Exception\AlreadySubmittedException
* @throws \Symfony\Component\Form\Exception\LogicException
* @throws \Symfony\Component\Form\Exception\UnexpectedTypeException
* @throws \OutOfBoundsException
*/
public function onPostSubmitService(FormEvent $event)
{
//get the closure that will add and initialise the user/price/billable/... fields
$formModifier = $this->getFormModifier();
// It's important here to fetch $event->getForm()->getData(), as
// $event->getData() will get the client data (that is, the ID)
// since we've added the listener to the child, we'll have to pass on
// the parent to the callback functions!
$formModifier($event->getForm()->getParent(), $event->getForm()->getData());
}
/**
* @param FormEvent $event
* @throws \Symfony\Component\Form\Exception\AlreadySubmittedException
* @throws \Symfony\Component\Form\Exception\LogicException
* @throws \Symfony\Component\Form\Exception\UnexpectedTypeException
* @throws \OutOfBoundsException
*/
public function onPostSubmitPaymentMode(FormEvent $event)
{
//get the closure that will add and initialise the user/price/billable/... fields
$formModifier = $this->getFormModifier();
// It's important here to fetch $event->getForm()->getData(), as
// $event->getData() will get the client data (that is, the ID)
// since we've added the listener to the child, we'll have to pass on
// the parent to the callback functions!
$formModifier($event->getForm()->getParent(), null, $event->getForm()->getData());
}
/**
* @param OptionsResolver $resolver
* @throws \Symfony\Component\OptionsResolver\Exception\AccessException
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => Operation::class
]);
}
/**
* Get the managers linked to the Service::defaultAllocationTeam
*
* @param Service $service
* @return User[]
*/
private function getDefaultTeamUserMangers(Service $service)
{
$users = [];
$userTeams = $service->getDefaultAllocationTeam()->getUserTeams();
foreach($userTeams as $userTeam) {
$user = $userTeam->getUser();
//TODO Maybe use the authorizationChecker to do this ?
//FIXME declare Roles constants in User entity
//only add the managers
if ($user->hasRole('ROLE_MANAGER')) {
//add user
$users[] = $user;
}
}
return $users;
}
/**s
* add the users field in the form
* preset the data using the selected service
*
* @param FormInterface $form
* @param Service|null $service
* @throws \Symfony\Component\Form\Exception\AlreadySubmittedException
* @throws \Symfony\Component\Form\Exception\LogicException
* @throws \Symfony\Component\Form\Exception\UnexpectedTypeException
* @throws \OutOfBoundsException
*/
private function addUserField(FormInterface $form, Service $service = null)
{
//default user field options
$options = $form->get('users')->getConfig()->getOptions();
//enable user field when the user selected a service
if ($service !== null) {
//get the preferred users (managers) for the service (using the defaultAllocationTeam service field)
$defaultTeamUsers = $this->getDefaultTeamUserMangers($service);
//enable the user field and set preferred choices
$options = array_merge($options, [
'disabled' => false,
'preferred_choices' => function($user) use ($defaultTeamUsers) {
return in_array($user, $defaultTeamUsers, true);
}
]);
//TODO preset users
//create the users field
$form->add('users', EntityType::class, $options);
}
}
/**
* Add price field
* Preset the price value using the service defaultUnitPrice field
*
* @param FormInterface $form
* @param Service|null $service
* @throws \Symfony\Component\Form\Exception\AlreadySubmittedException
* @throws \Symfony\Component\Form\Exception\LogicException
* @throws \Symfony\Component\Form\Exception\UnexpectedTypeException
* @throws \OutOfBoundsException
*/
private function addPriceField(FormInterface $form, Service $service = null)
{
$options = $form->get('unitPrice')->getConfig()->getOptions();
//enable the price field
if ($service !== null) {
$options = array_merge($options, [
'disabled' => false,
//set the price in the form
'empty_data' => $service->getDefaultUnitPrice()
]);
$form->add('unitPrice', MoneyType::class, $options);
}
}
/**
* Add billable field
* Preset the isBillable value using the Service::isBillable function
*
* @param FormInterface $form
* @param Service|null $service
* @throws \Symfony\Component\Form\Exception\AlreadySubmittedException
* @throws \Symfony\Component\Form\Exception\LogicException
* @throws \Symfony\Component\Form\Exception\UnexpectedTypeException
* @throws \OutOfBoundsException
*/
private function addBillableField(FormInterface $form, Service $service = null)
{
//get the default options of the
$options = $form->get('billable')->getConfig()->getOptions();
if ($service !== null) {
//check billable checkbox only if isBillable is true
if ($service->isBillable()) {
//only add the empty_data option if the service is billable
//note: if we add 'empty_data' with false, the checkbox will still be checked
$options['empty_data'] = true;
}
//enable the billable field
$options = array_merge($options, [
'disabled' => false,
]);
//create the form field
$form->add('billable', CheckboxType::class, $options);
}
}
/**
* Add ExtraAllowedQuantity field
* Activate this field only if the user set PAY_AS_YOU paymentMode
*
* @param FormInterface $form
* @param null|string $paymentMode
* @throws \Symfony\Component\Form\Exception\AlreadySubmittedException
* @throws \Symfony\Component\Form\Exception\LogicException
* @throws \Symfony\Component\Form\Exception\UnexpectedTypeException
* @throws \OutOfBoundsException
*/
private function addExtraAllowedQuantityField(FormInterface $form, string $paymentMode = null)
{
$options = $form->get('extraAllowedQuantity')->getConfig()->getOptions();
//enable the extraAllowedQuantity field only when PAY_AS_YOU_GO is selected
if ($paymentMode === PaymentMode::PAY_AS_YOU_GO) {
$options['required'] = true;
$options['disabled'] = false;
$form->add('extraAllowedQuantity', IntegerType::class, $options);
}
}
/**
* Add ExtraPrice field
* Activate this field only if the user selected PAY_AS_YOU_GO paymentMode
*
* @param FormInterface $form
* @param string|null $paymentMode
* @throws \Symfony\Component\Form\Exception\AlreadySubmittedException
* @throws \Symfony\Component\Form\Exception\LogicException
* @throws \Symfony\Component\Form\Exception\UnexpectedTypeException
* @throws \OutOfBoundsException
*/
private function addExtraPriceField(FormInterface $form, string $paymentMode = null)
{
$options = $form->get('extraPrice')->getConfig()->getOptions();
//enable the extraAllowedQuantity field only when PAY_AS_YOU_GO is selected
if ($paymentMode === PaymentMode::PAY_AS_YOU_GO) {
$options['required'] = true;
$options['disabled'] = false;
$form->add('extraPrice', MoneyType::class, $options);
}
}
/**
* @return \Closure
* @throws \OutOfBoundsException
* @throws \Symfony\Component\Form\Exception\UnexpectedTypeException
* @throws \Symfony\Component\Form\Exception\LogicException
* @throws \Symfony\Component\Form\Exception\AlreadySubmittedException
*/
private function getFormModifier()
{
return function (FormInterface $form, Service $service = null, string $paymentMode = null) {
//Note: As we use this form as a "prototype" in Package & RFOP,
//The event->getData will return null on the prototype creation (i.e onPreSetData event)
//add the user field to the form
$this->addUserField($form, $service);
//add the billable field to the form
$this->addBillableField($form, $service);
//add the price field to the form
$this->addPriceField($form, $service);
//add the extra price field to the form
$this->addExtraPriceField($form, $paymentMode);
//add the extra quantity field to the form
$this->addExtraAllowedQuantityField($form, $paymentMode);
};
}
}
请记住,您应该将侦听器提取到某些事件中 订阅者以获得更易读的FormType