获取窗体扩展中实体的初始值



在我的更新表单中,我想在输入上添加一个数据属性,该属性将包含实体的初始值。这样,当用户修改输入时,我将能够突出显示输入。

最后,只会突出显示用户修改的输入。

我只想在更新中使用它,而不是在创建中使用它。

为此,我创建了一个如下所示的表单扩展:

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包含新信息,而不是我要更新的实体的值。

如何获取原始实体的值?

  1. 我不想在这里发出数据库请求。
  2. 我想我可以使用FormEvents::POST_SET_DATA事件,然后将实体值保存在会话中并在buildView中使用这些值。
  3. 我还可以在我的选项解析器中给出一个新选项来请求初始实体。
  4. 是否可以让实体的原始数据直接形成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.

选择:

  1. 您不是为表单提供$customer,而是创建一个包含相同值的客户 DTO,但在更改时不会自动更改原始对象。如果表单验证失败,您可以重新生成 DTO。

  2. 而不是refresh($customer),它很可能会运行另一个查询(如果您有缓存,除非可能不会(,您可以通过DefaultCacheEntityHydrator自己缓存客户,您必须创建自己的 EntityCacheKey 对象(不是真的很难(,生成一个缓存条目(DefaultCacheEntityHydrator::buildCacheEntry()在上面的((*))(并在需要恢复它时恢复该条目。免责声明:我不知道这是否/如何与集合(即实体可能有的集合属性(一起工作。

话虽如此...如果您出于某种原因确实想要表单扩展,您可能希望使用PRE_SET_DATA处理程序来形成事件,该处理程序将数据存储在表单类型对象中,然后在 buildView 上使用这些值。我不会在会话中存储一些东西,因为我看不出有必要......不过,您对数据库查询的厌恶令人困惑,如果这是您所有恶作剧的主要原因

最后,我设法让它工作,但我并不完全相信我所做的事情。

无法从表单中获取原始数据或添加新属性(表单在表单扩展中是只读的(。

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

我在这里所做的只是在会话中按其名称注册表单值。 如果它是一个对象,我需要克隆它,因为表单将在流程的后期对其进行修改,并且我想使用表单的原始状态。

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());
}

也许代码示例会对任何人有所帮助! 如果有人有更好的选择,请不要犹豫,发布它!

最新更新