使用 AJAX 保存来自 Symfony 4 表单的数据

Persisting data from a Symfony 4 form with AJAX

我正在使用 Symfony 表单修改我的 "Context" 实体,但我无法从 ajax 请求获取数据以修改我的实体。我找到了另一种方法来做我需要的事情(如下),但我没有使用 handleRequest :

上下文控制器:

    public function share(Request $request, Context $context, UsersRepository $usersRepository)
    {
        $users = $usersRepository->findAll();
        $form = $this->createForm(ShareContextType::class, $context, ['users' => $users]);

        // Envoie du formulaire de partage de contexte en ajax
        if ($request->isXmlHttpRequest() && $request->isMethod('GET')) {
            $template = $this->render('context/share.html.twig', [
                'form' => $form->createView(),
            ])->getContent();
            $json = json_encode($template);

            return new JsonResponse($json);

        // Requête post avec les id's des utilisateurs pour le partage de context
        } elseif ($request->isXmlHttpRequest() && $request->isMethod('POST')) {
            $reponse = $request->getContent();
            $json = json_decode($reponse);
            $formArray = [];

            // Ajoute chaque utilisateur aux tableaux d'utilisateurs de ce contexte
            foreach ($json as $userId) {
                $user = $usersRepository->find($userId);
                $context->addUser($user);
                $formArray[] = $user;
            }

            // Si l'utilisateur n'est pas présent dans le tableaux de la requête
            // alors supprime l'utilisateur du tableau du contexte
            foreach ($context->getUsers() as $user) {
                if (!in_array($user, $formArray, true)) {
                    $context->removeUser($user);
                }
            }
            $this->getDoctrine()->getManager()->flush();

            return new JsonResponse(['success' => 'Ok']);
        }

我的 JS:

    $('#share-context-form').submit(function(e) {
        e.preventDefault();
        formData = $('#share_context_users').val();
        sendShareContext(formData, $('#context-id').val());
    })

function sendShareContext(formData, contextId) {
    $.ajax({
        type: 'POST',
        url: '/contextes/' + contextId + '/share',
        data: JSON.stringify(formData),
        headers: {
            'Content-Type': 'application/json',
            Accept: 'application/json'
        },
        async: true,

(...)

它工作得很好。但是如果我尝试在我的 JS 中做:

$formData = $('#share-context-form').serialize();

并在我的 Controller 中获取序列化对象来执行类似的操作:

$form->handleRequest($request);
if ($form->isValid()) {
(...)
}

它不起作用,因为当我尝试获取 $request 内容时,我实际上得到了类似这样的内容:

$request->getContent() // Output : "share_context%5Busers%5D%5B%5D=25&share_context%5B…en%5D=vrWCjceU9LVGVrSKyMggRdXNNjeG4KTBDLW0HqIh3aQ"

我也尝试用 Symfony 的 SerializerInterface 反序列化它,做:

$serializer->deserialize($form, ShareContextType::class, 'json');

但是也没用。 如果您有一些想法,我将不胜感激。谢谢!

查看

听起来你需要 serializeArray() 而不是你的 JS 中的 serialize()。示例:

$('#share-context-form').submit(function(e) {

    e.preventDefault();

    $.post('/contextes/' + $('#context-id').val() + '/share', $(this).serializeArray(), function(data, textStatus, xhr) {
        // Success handler
    }).fail(function(xhr, textStatus, errorThrown) {
        // Fail handler (optional)
    }).always(function() {
        // Finished handler, success or fail
    });

});

对于此类提交,您可能希望 a) 验证上下文 ID 是否存在,以及 b) 防止意外双击:

var shareContextFormSubmitting = false;

$('#share-context-form').submit(function(e) {

    e.preventDefault();

    if (shareContextFormSubmitting) {
        // Optional: Prevents multiple forms submitting in parallel (blocks double click mistake)
        return false;
    }

    var contentId = $('#context-id').val();
    if (typeof contentId != "number" || isNaN(contentId) || contentId < 1) {
        // Do some alert or whatev because contentId is empty, I'm guessing you expect this to be an integer at least 1
        return false;
    }

    shareContextFormSubmitting = true;
    $.post('/contextes/' + contentId + '/share', $(this).serializeArray(), function(data, textStatus, xhr) {
        // Success handler
    }).fail(function(xhr, textStatus, errorThrown) {
        // Fail handler (optional)
    }).always(function() {
        shareContextFormSubmitting = false;
    });

});

控制器

问题也出在你的控制器上:

// Requête post avec les id's des utilisateurs pour le partage de context
} elseif ($request->isXmlHttpRequest() && $request->isMethod('POST')) {
    $reponse = $request->getContent();
    $json = json_decode($reponse);
    $formArray = [];

jQuery 的 serializeArray() 采用一种形式,并为 "application/x-www-form-urlencoded" 编码的常规 HTTP POST 请求准备一个变量。 (无关:这就是为什么你不能使用 serializeArray() 上传文件字段。)

你试图把它读成 JSON,但实际上它是一个 "application/x-www-form-urlencoded" 的字符串。你只需要为此使用 Symfony 的标准函数。我建议按如下方式更新您的控制器功能:

我还没有对此进行测试,因此可能需要进行一些调整。我也不会说法语,所以我试图保留的一些评论和验证错误可能没有任何意义。

use Symfony\Component\Form\FormError;

public function share(Request $request, Context $context, UsersRepository $usersRepository)
{
    $users = $usersRepository->findAll();
    $form = $this->createForm(ShareContextType::class, $context, ['users' => $users]);
    $form->handleRequest($request);

    // Gérer la soumission du formulaire, que ce soit AJAX ou standard
    if ($form->isSubmitted()) {
        // Ajoute chaque utilisateur aux tableaux d'utilisateurs de ce contexte
        $formArray = [];
        $usersField = $form->get('users');
        $usersSubmitted = $usersField->getData();

        if (is_array($usersSubmitted) && count($usersSubmitted) >= 1) {
            foreach ($usersSubmitted as $i => $userId) {
                if (($userId = intval($userId)) < 1) {
                    $usersField->addError(new FormError(sprintf("ID utilisateur %d non valide.", ($i + 1))));
                    continue;
                }

                if (!($user = $usersRepository->find($userId))) {
                    $usersField->addError(new FormError(sprintf("L'utilisateur %d avec l'ID # %d est introuvable..", ($i + 1), $userId)));
                    continue;
                }

                $context->addUser($user);
                $formArray[] = $user;
            }
        } else {
            $usersField->addError(new FormError("Utilisateurs non spécifiés."));
        }

        // Enregistrer uniquement s'il n'y a pas eu d'erreurs
        if ($form->isValid()) {
            // Si l'utilisateur n'est pas présent dans le tableaux de la requête alors supprime l'utilisateur du tableau du contexte
            foreach ($context->getUsers() as $user) {
                if (!in_array($user, $formArray, true)) {
                    $context->removeUser($user);
                }
            }

            $this->getDoctrine()->getManager()->flush();
            return $request->isXmlHttpRequest() ? new JsonResponse(['success' => 'Ok']) : $this->addFlash('success', 'Enregistré avec succès.');
        }
    }

    // Envoyez le formulaire de partage de contexte en AJAX ou en standard
    $responseContent =  $this->renderView('context/share.html.twig', [
        'form' => $form->createView(),
    ]);

    return $request->isXmlHttpRequest() ? new JsonResponse(json_encode($responseContent)) : new Response($responseContent);
}

这样做的好处:

  • GET 请求可以 return 常规和 AJAX 请求的相同模板。
  • POST 请求可以是常规的,或者 AJAX 使用 serializeArray()
  • 这允许验证表单,并且仅当指定的每个用户都被赋予有效 ID 并且存在时才保存更改。
  • 当验证失败时,表单模板将被 return 编辑并标记验证错误。您还会看到是否提供了多个错误的用户 ID。
  • 当验证通过时,JSON ['success' => "Ok"] 将到达,但如果您不想要表单,您可能希望将 addFlash() 调用更改为 redirectToRoute()再次出现。