ZetCode

Symfony 保留表单值

最后修改时间:2025年3月3日

Symfony 保留表单值教程演示了如何在表单提交失败后,在表单提交后保留表单值。在本教程中,我们进行传统的表单提交;我们不使用表单构建器。

Symfony

Symfony 是一套可重用的 PHP 组件,也是一个用于 Web 项目的 PHP 框架。Symfony 于 2005 年发布为自由软件。Symfony 的最初作者是 Fabien Potencier。Symfony 深受 Spring Framework 的启发。

保留表单值

当用户提交表单时,应用程序会对其进行验证。当验证失败时,应用程序会将用户重定向回表单,并显示验证错误。保留用户已输入的表单值是一个好习惯。

Symfony 保留表单值示例

在示例中,我们有一个带有两个字段的简单表单:name 和 email。提交表单后,我们会检查 CSRF 保护并使用 Symfony 的 Validator 验证输入值。我们将输入的值存储在会话中,以便在提交失败时将其检索回来。

设置应用程序

我们首先使用 composer 设置应用程序。

$ composer create-project symfony/skeleton formkeepvals "^7.2"
$ cd formkeepvals

我们创建一个新的 Symfony 7.2 skeleton 项目,然后进入新创建的项目目录。

$ composer require symfony/twig-bundle symfony/validator symfony/annotations

我们安装三个基本的 Symfony 包:twig-bundlevalidatorannotations

$ composer require symfony/security-csrf symfony/monolog-bundle

security-csrf 包是防止跨站请求伪造所必需的,而 monolog-bundle 用于日志记录。

$ composer require symfony/property-access

我们安装 PropertyAccess 组件,它用于方便地读取和写入对象和数组的属性/键。

$ composer require symfony/maker-bundle symfony/web-server-bundle --dev

我们为 Symfony 7.2 安装 maker bundle 和开发服务器。

$ php bin/console make:controller HomeController

我们创建一个 HomeController。该控制器将表单发送到客户端。

src/Controller/HomeController.php
<?php

declare(strict_types=1);

namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;

class HomeController extends AbstractController
{
    #[Route('/home', name: 'home')]
    public function index(): Response
    {
        return $this->render('home/index.html.twig');
    }
}

这是一个简单的控制器,它将包含 Web 表单的视图发送给用户。

$ php bin/console make:controller MessageController

我们创建一个 MessageController 来响应表单提交。

src/Controller/MessageController.php
<?php

declare(strict_types=1);

namespace App\Controller;

use App\Service\ValidationService;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;

class MessageController extends AbstractController
{
    #[Route('/message', name: 'message', methods: ['POST'])]
    public function index(Request $request, ValidationService $validator): Response
    {
        $token = $request->request->get('token', '');

        if (!$validator->validateToken($token)) {
            return new Response(
                'Operation not allowed',
                Response::HTTP_BAD_REQUEST,
                ['content-type' => 'text/plain']
            );
        }

        $name = $request->request->get('name', '');
        $email = $request->request->get('email', '');

        $input = ['name' => $name, 'email' => $email];
        $errorMessages = $validator->validateInput($input);

        if (count($errorMessages) > 0) {
            $session = $request->getSession();
            $session->set('name', $name);
            $session->set('email', $email);

            foreach ($errorMessages as $key => $val) {
                $this->addFlash($key, $val);
            }

            return $this->redirectToRoute('home');
        }

        return new Response(
            'User saved',
            Response::HTTP_OK,
            ['content-type' => 'text/plain']
        );
    }
}

MessageController 中,我们检查 CSRF 令牌,验证表单输入值,并将响应发送回客户端。

src/Service/ValidationService.php
<?php

declare(strict_types=1);

namespace App\Service;

use Psr\Log\LoggerInterface;
use Symfony\Component\Security\Csrf\CsrfToken;
use Symfony\Component\Validator\Constraints as Assert;
use Symfony\Component\Validator\Validator\ValidatorInterface;
use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface;
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;

class ValidationService
{
    public function __construct(
        private readonly CsrfTokenManagerInterface $tokenManager,
        private readonly ValidatorInterface $validator,
        private readonly PropertyAccessorInterface $accessor,
        private readonly LoggerInterface $logger
    ) {
    }

    public function validateToken(string $token): bool
    {
        $csrfToken = new CsrfToken('myform', $token);
        $isValid = $this->tokenManager->isTokenValid($csrfToken);

        if (!$isValid) {
            $this->logger->error('CSRF failure');
        }

        return $isValid;
    }

    /** @return array<string, string> */
    public function validateInput(array $input): array
    {
        $constraints = new Assert\Collection([
            'name' => [
                new Assert\Length(['min' => 2]),
                new Assert\NotBlank(),
            ],
            'email' => [
                new Assert\Email(),
                new Assert\NotBlank(),
            ],
        ]);

        $violations = $this->validator->validate($input, $constraints);

        if (count($violations) > 0) {
            $this->logger->info('Validation failed');
            $messages = [];

            foreach ($violations as $violation) {
                $this->accessor->setValue(
                    $messages,
                    $violation->getPropertyPath(),
                    $violation->getMessage()
                );
            }

            return $messages;
        }

        return [];
    }
}

ValidationService 检查 CSRF 令牌并验证输入。

templates/home/index.html.twig
{% extends 'base.html.twig' %}

{% block title %}Home page{% endblock %}

{% block stylesheets %}
<style>
    .topmargin {
        margin-top: 10px;
    }
</style>
{% endblock %}

{% block body %}

<section class="ui container topmargin">

    <form class="ui form" action="{{ path('message') }}" method="post">

        <input type="hidden" name="token" value="{{ csrf_token('myform') }}" />

        {% for msg in app.flashes('name') %}
        <div class="ui small red message">
            {{ msg }}
        </div>
        {% endfor %}

        <div class="field">
            <label>Name:</label>
            <input type="text" name="name" value="{{ app.session.get('name')|default('') }}">
        </div>

        {% for msg in app.flashes('email') %}
        <div class="ui small red message">
            {{ msg }}
        </div>
        {% endfor %}

        <div class="field">
            <label>Email</label>
            <input type="text" name="email" value="{{ app.session.get('email')|default('') }}">
        </div>

        <button class="ui button" type="submit">Send</button>

    </form>

</section>

{% endblock %}

主页有一个表单。该表单包含两个字段:nameemail

templates/base.html.twig
<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>{% block title %}Welcome!{% endblock %}</title>
        <link href="https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/2.4.1/semantic.min.css"
                 rel="stylesheet">
        {% block stylesheets %}{% endblock %}
    </head>
    <body>
        {% block body %}{% endblock %}

        <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
        <script src="https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/2.3.1/semantic.min.js"></script>
        {% block javascripts %}{% endblock %}
    </body>
</html>

这是基础的 Twig 模板。它包含了 Semantic UI CSS 框架。

在本教程中,我们验证了一个 Symfony 7.2 应用程序中的简单表单。

列出 所有 Symfony 教程