所以,这是我的问题:我必须根据其基础数据向表单添加一个字段,但我必须向该字段添加一个数据转换器。
我认为解决方案很简单,只需向表单添加一个 PRE_SET_DATA 事件监听器(只是为了访问底层数据)并在监听器内添加字段和转换器。但我无法在侦听器内添加变压器,因为表单已经被锁定。
我尝试了很多解决方法,但无法解决。她是我的代码:
$builder->...
->add(
$builder->create('date', 'choice', array(
'label' => 'form.game.date',
'empty_value' => 'form.game.date',
'required' => false,
'choices' => array(
'2014-04-10' => '10/Apr', // just to test
'2014-04-11' => '11/Apr',
)
))
->addModelTransformer(new DateToStringTransformer())
);
$builder->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event) use ($builder) {
$game = $event->getData();
$form = $event->getForm();
$period = new \DatePeriod(
$game->getTournament()->getBeginDate(),
new \DateInterval('P1D'),
$game->getTournament()->getEndDate()->add(new \DateInterval('P1D'))
);
$dates = array();
foreach($period as $date){
$dates[$date->format("Y-m-d")] = $date->format("j/M");
}
$form->add('date', 'choice', array(
'label' => 'form.game.date',
'choices' => $dates,
));
});
当我将日期字段添加到事件侦听器内的表单时,之前添加的数据字段将被替换,因此它是数据转换器...
有办法吗?
我写了一些测试并更新了你的代码。检查我是否正确理解你的问题。
SomeTypeTest.php:
<?php
class SomeTypeTest extends TypeTestCase
{
/**
* @test
*/
public function testSubmitValidData()
{
$begin = new \DateTime();
$formData = array(
'date' => '2014-01-15'
);
$type = new SomeType();
$form = $this->factory->create($type);
$form->submit($formData);
$this->assertTrue($form->isSynchronized());
$this->assertEquals(['date' => \DateTime::createFromFormat('Y-m-d', '2014-01-15')], $form->getData());
$view = $form->createView();
$children = $view->children;
foreach (array_keys($formData) as $key) {
$this->assertArrayHasKey($key, $children);
}
}
}
SomeType.php:
<?php
class SomeType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event) use ($builder) {
//$game = $event->getData();
$form = $event->getForm();
$period = new \DatePeriod(
\DateTime::createFromFormat('Y-m-d', '2014-01-01'), // for test
new \DateInterval('P1D'),
\DateTime::createFromFormat('Y-m-d', '2014-01-30') // for test
);
$dates = array();
foreach ($period as $date) {
$dates[$date->format("Y-m-d")] = $date->format("j/M");
}
$form->add($builder->create('date', 'choice', array(
'label' => 'form.game.date',
'empty_value' => 'form.game.date',
'auto_initialize' => false,
'required' => false,
'choices' => $dates
))
->addModelTransformer(new DateToStringTransformer())->getForm()
);
});
}
public function getName()
{
return 'st';
}
}
DateToStringTransformer.php:
<?php
class DateToStringTransformer implements DataTransformerInterface
{
/**
* @param mixed $value
* @return mixed|void
*/
public function transform($value)
{
if (!$value) {
return null;
}
return $value->format('Y-m-d');
}
/**
* @param mixed $value
* @return mixed|void
*/
public function reverseTransform($value)
{
return \DateTime::createFromFormat('Y-m-d', $value);
}
}
我成功地通过创建始终添加数据转换器的自定义类型来使其工作。然后我可以从任何事件监听器调用“form->add('date', 'my_type',..)”,而不会丢失数据转换器。
MyType.php
class MyType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('field1')
->add('field2')
...;
$builder->addEventSubscriber(new AddDateSubscriber());
}
}
自定义类型.php
class DateChoiceType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->addModelTransformer(new DateToStringTransformer());
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'invalid_message' => 'The selected date does not exist',
));
}
public function getParent()
{
return 'choice';
}
public function getName()
{
return 'date_choice';
}
}
每次我向表单添加 date_choice 类型时,数据转换器也会被添加。
class AddDateSubscriber implements EventSubscriberInterface
{
public static function getSubscribedEvents()
{
return array(FormEvents::PRE_SET_DATA => 'preSetData');
}
public function preSetData(FormEvent $event)
{
$game = $event->getData();
$form = $event->getForm();
$endDate = \DateTime::createFromFormat('Y-m-d', $game->getTournament()->getEndDate()->format('Y-m-d'));
$period = new \DatePeriod(
$game->getTournament()->getBeginDate(),
new \DateInterval('P1D'),
$endDate
);
$dates = array();
foreach($period as $date){
$dates[$date->format("Y-m-d")] = $date->format("j/M");
}
$form->add('date', 'date_choice', array(
'label' => 'form.game.date.label',
'empty_value' => 'form.game.date.none',
'required' => false,
'choices' => $dates,
));
}
}
DateToStringTransformer.php
class DateToStringTransformer implements DataTransformerInterface
{
public function transform($date)
{
if (null === $date) {
return "";
}
return $date->format("Y-m-d");
}
public function reverseTransform($stringDate)
{
if (!$stringDate) {
return null;
}
$date = \DateTime::createFromFormat('Y-m-d', $stringDate);
if (false === $date) {
throw new TransformationFailedException('Sting to date transformation failed!');
}
return $date;
}
}
希望这会对某人有所帮助。
真实案例:我有一个表单类型,其中有一个选择列表,其中包含描述 HTML 输入类型(文本、文本区域、选择等...)的预填充值
选择 select 选项后,应显示名为 possibleOptions 的第二个字段。
此外,我有一个 transformer 应该应用于第二个字段,它将输入标签值转换为数组,反之亦然。
默认情况下,当我执行 creation 时,第二个字段将被隐藏,直到选择 select 选项。但是,在 edit 操作中,默认情况下应显示第二个字段,因为我选择了 select 选项。
我使用事件监听器 PRE_SET_DATA 来自定义渲染。
这是我的完整解决方案:
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
->add('type', ChoiceType::class, [
"label" => "Type du champs",
"choices" => [
'Texte' => FieldTypeEnum::TEXT->value,
'Email' => FieldTypeEnum::EMAIL->value,
'Tél' => FieldTypeEnum::PHONE->value,
'Lien' => FieldTypeEnum::URL->value,
'Nombre' => FieldTypeEnum::NUMBER->value,
'Date' => FieldTypeEnum::DATE->value,
'Code secret' => FieldTypeEnum::SECRET_CODE->value,
'Liste déroulante' => FieldTypeEnum::SELECT->value,
'Text Long' => FieldTypeEnum::TEXTAREA->value,
]
])
->add('possibleOptions', TextType::class, [
'label' => 'Choix possibles',
'attr' => [
'data-tags' => true
],
'row_attr' => [
'class' => 'd-none'
],
'required' => true
])
;
$builder->get('possibleOptions')->addModelTransformer($this->stringToArrayTransformer);
$builder->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event) use ($builder) {
if ($event->getData() instanceof Field) {
/**
* @var Field $field
*/
$field = $event->getData();
$form = $event->getForm();
if ($field->getType() === FieldTypeEnum::SELECT->value && $field->getId()) {
$form->add(
$builder->create(
'possibleOptions', TextType::class, [
'label' => 'Choix possibles',
'attr' => [
'data-tags' => true
],
'required' => true,
'auto_initialize' => false,
]
)->addModelTransformer($this->stringToArrayTransformer)->getForm()
);
}
}
});
}
希望这能有所帮助!