在我的更新表单中,我想在输入上添加一个数据属性,它将包含实体的初始值。这样,当用户修改输入时,我就可以高亮显示该输入。
最后,只有用户修改的输入才会被高亮显示。
我希望只在更新时使用,而不是在创建时使用。
为此,我创建了一个这样的表单扩展。
class IFormTypeExtension extends AbstractTypeExtension
{
...
public static function getExtendedTypes()
{
//I want to be able to extend any form type
return [FormType::class];
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'is_iform' => false,
'is_iform_modification' => function (Options $options) {
return $options['is_iform'] ? null : false;
},
]);
$resolver->setAllowedTypes('is_iform', 'bool');
$resolver->setAllowedTypes('is_iform_modification', ['bool', 'null']);
}
public function buildView(FormView $view, FormInterface $form, array $options)
{
if (!$options['is_iform'] && !$this->isParentIForm($form)) {
return;
}
//We need to add the original value in the input as data-attributes
if (is_string($form->getViewData()) || is_int($form->getViewData())) {
$originValue = $form->getViewData();
} elseif (is_array($form->getViewData())) {
if (is_object($form->getNormData())) {
$originValue = implode('###', array_keys($form->getViewData()));
} elseif (is_array($form->getNormData()) && count($form->getNormData()) > 0 && is_object($form->getNormData()[0])) {
$originValue = implode('###', array_keys($form->getViewData()));
} else {
$originValue = implode('###', $form->getViewData());
}
} else {
//There's no value yet
$originValue = '';
}
$view->vars['attr'] = array_merge($view->vars['attr'], ['data-orig-value' => $originValue]);
}
private function isParentIForm(FormInterface $form)
{
if (null === $form->getParent()) {
return $form->getConfig()->getOption('is_iform');
}
return $this->isParentIForm($form->getParent());
}
}
如你所见,在buildView方法中,我从ViewData中得到了originValue。
在很多情况下,这很好用。
但如果我的表单中出现任何验证错误,或者如果我通过AJAX重新加载表单,则会出现以下情况 ViewData
包含新的信息,而不是我要更新的实体的值。
我怎样才能得到原来实体的值?
FormEvents::POST_SET_DATA
事件,然后将实体值保存在session中,并将这些值用在 buildView
.buildView
? (如果我没说错的话,这意味着在我们调用之前的形式是? handleRequest
方法)。)有人想用控制器做个例子。我不认为这真的很有趣,因为使用FormExtension,代码会自动添加。但无论如何,以下是我如何在我的控制器中创建一个表单。
$form = $this->createForm(CustomerType::class, $customer)->handleRequest($request);
在CustomerType中,我将使用configureOptions()添加'is_iform'键。
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
"translation_domain" => "customer",
"data_class" => Customer::class,
'is_iform' => true //This line will activate the extension
]);
}
这可能是一个有观点的答案。也可能有更好的方法.我不是你的表单扩展的忠实粉丝,因为它真的很复杂,不清楚发生了什么,至少在我的眼中。
我的建议是。当表单提交发生时,在你的控制器中,你应该做以下的操作
// ((*)) maybe store customer, see below
$form = $this->createForm(CustomerType::class, $customer);
$form->handleRequest($request);
if($form->isSubmitted() && $form->isValid()) {
// easy case, you got this.
$em->flush();
return $this->redirect(); // or another response
} elseif($form->isSubmitted()) {
// form was submitted with errors, have to refresh entity!
// REFRESH - see discussion below for alternatives
$em->refresh($customer);
// then create form again with original values:
$form = $this->createForm(CustomerType::class, $customer);
}
// other stuff
return $this->render(..., ['form' => $form->createView(), ...]);
所以,从本质上讲,当表单验证失败时,你刷新实体并重新创建表单,避免了实体状态改变的问题。我相信这种方法最终比黑客入侵表单神奇地不更新值或重新设置旧的值更容易。
现在的问题是:如何刷新一个实体?最简单的方法:从数据库中重新加载。
$em->refresh($customer); // easiest approach, will likely run another query.
替代品:
取而代之的是: $customer
到表单,您创建了一个包含相同值的客户DTO,但在更改时不会自动更改原始对象。如果表单验证失败,您可以直接重新生成DTO。
取而代之的是 refresh($customer)
,这很可能会运行另一个查询(如果你有缓存的话,也许不会),你可以自己通过一个 DefaultCacheEntityHydrator
你将不得不创建自己的 EntityCacheKey 对象(其实并不难),生成一个缓存条目(DefaultCacheEntityHydrator::buildCacheEntry()
在 ((*))
以上),并在你需要的时候恢复该条目。声明:我不知道这对集合(即集合属性,实体可能有)是否有效。
也就是说......如果你真的真的想要一个表单扩展,不管是出于什么原因,你可能想要一个带有PRE_SET_DATA处理程序的表单事件,该处理程序在表单类型对象中存储数据,然后在buildView中使用这些值。我不会在会话中存储一些东西,因为我觉得没有必要......不过你对db查询的厌恶是令人费解的,如果这是你所有诡计的主要原因的话。
最后,我设法让它工作,但我不完全相信我所做的。
它不可能从表单中获取原始数据,也不可能添加一个新的属性(表单扩展中的表单是只读的)。
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->addEventListener(
FormEvents::POST_SET_DATA,
function (FormEvent $event) {
$form = $event->getForm();
if ('_token' === $form->getName()) {
return;
}
$data = $event->getData();
$this->session->set('iform_'.$form->getName(), is_object($data) ? clone $data : $data);
}
);
}
我在这里做的,只是简单地在session中用它的名字注册表单的值.如果它是一个对象,我需要克隆它,因为表单在以后的过程中会修改它,我想用表单的原始状态工作。
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'is_iform' => false,
'is_iform_modification' => function (Options $options) {
return $options['is_iform'] ? null : false;
},
]);
$resolver->setAllowedTypes('is_iform', 'bool');
$resolver->setAllowedTypes('is_iform_modification', ['bool', 'null']);
}
配置选项没有改变.然后,根据值类型,我创建我的 "数据-原始值"。
public function buildView(FormView $view, FormInterface $form, array $options)
{
if (!$options['is_iform'] && !$this->isParentIForm($form)) {
return;
}
$propertyValue = $this->session->get('iform_'.$form->getName());
$originValue = '';
try {
if (null !== $propertyValue) {
//We need to add the original value in the input as data-attributes
if (is_bool($propertyValue)) {
$originValue = $propertyValue ? 1 : 0;
} elseif (is_string($propertyValue) || is_int($propertyValue)) {
$originValue = $propertyValue;
} elseif (is_array($propertyValue) || $propertyValue instanceof Collection) {
if (is_object($propertyValue)) {
$originValue = implode('###', array_map(function ($object) {
return $object->getId();
}, $propertyValue->toArray()));
} elseif (is_array($propertyValue) && count($propertyValue) > 0 && is_object(array_values($propertyValue)[0])) {
$originValue = implode('###', array_map(function ($object) {
return $object->getId();
}, $propertyValue));
} else {
$originValue = implode('###', $propertyValue);
}
} elseif ($propertyValue instanceof DateTimeInterface) {
$originValue = \IntlDateFormatter::formatObject($propertyValue, $form->getConfig()->getOption('format', 'dd/mm/yyyy'));
} elseif (is_object($propertyValue)) {
$originValue = $propertyValue->getId();
} else {
$originValue = $propertyValue;
}
}
} catch (NoSuchPropertyException $e) {
if (null !== $propertyValue = $this->session->get('iform_'.$form->getName())) {
$originValue = $propertyValue;
$this->session->remove('iform_'.$form->getName());
} else {
$originValue = '';
}
} finally {
//We remove the value from the session, to not overload the memory
$this->session->remove('iform_'.$form->getName());
}
$view->vars['attr'] = array_merge($view->vars['attr'], ['data-orig-value' => $originValue]);
}
private function isParentIForm(FormInterface $form)
{
if (null === $form->getParent()) {
return $form->getConfig()->getOption('is_iform');
}
return $this->isParentIForm($form->getParent());
}
也许代码示例将帮助任何人!如果有人有一个更好的选择,不要犹豫,把它贴出来!。