Symfony 保留表单值

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

Symfony

Symfony 是一组可重用的 PHP 组件和一个用于 Web 项目的 PHP 框架。 Symfony 于 2005 年发布为免费软件。Symfony 的原始作者是 Fabien Potencier。 Symfony 受到 Spring 框架的极大启发。

保留表单值

用户提交表单后,将由应用进行验证。 当验证失败时,应用将用户重定向回表单,显示验证错误。 最好将已输入的值保留在表格中。

Symfony 保留表单值示例

在示例中,我们有一个简单的表单,其中包含两个字段:名称和电子邮件。 提交表单后,我们检查 CSRF 保护并使用 Symfony 的Validator验证输入值。 我们将输入的值存储到会话中,以在提交失败时取回它们。

建立应用

我们首先使用 composer 建立应用。

$ composer create-project symfony\skeleton formkeepvals
$ cd formkeepvals

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

$ composer require twig annot validator

我们安装了三个基本的 Symfony 软件包:twigannotvalidator。 程序包可能具有别名。 例如,symfony/validator具有两个别名:validatorvalidation

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

跨站点请求伪造需要security-csrf软件包,而日志记录则需要monolog-bundle软件包。

$ composer require symfony/property-access

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

$ composer require maker server --dev

我们安装制造商组件和开发服务器。

$ php bin/console make:controller HomeController

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

src/Controller/HomeController.php

<?php

namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Routing\Annotation\Route;

class HomeController extends AbstractController
{
    /**
     * @Route("/home", name="home")
     */
    public function index()
    {
        return $this->render('home/index.html.twig');
    }
}

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

$ php bin/console make:controller MessageController

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

src/Controller/MessageController.php

<?php

namespace App\Controller;

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

class MessageController extends AbstractController
{
    /**
     * @Route("/message", name="message")
     */
    public function index(Request request, ValidationServicevalidator)
    {
        token =request->get("token");

        valid =validator->validateToken(token);

        if (!valid) {

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

            returnthis->redirectToRoute('home');

        } else {

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

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

public function index(Request request, ValidationServicevalidator)
{

验证委派给ValidationService,后者被注入到方法中。

$token = $request->get("token");

$valid = $validator->validateToken($token);

if (!$valid) {

    return new Response("Operation not allowed", Response::HTTP_BAD_REQUEST,
        ['content-type' => 'text/plain']);
}

我们获得 CSRF 令牌并对其进行验证。 如果验证失败,我们将带有错误消息的响应发送回客户端。

$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 askey => val) {this->addFlash(key,val);
}

我们将消息添加到 Flash 包中; 闪存袋用于存储临时消息,例如我们的验证消息。

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

我们使用redirectToRoute()重定向回表单。

src/Service/ValidationService.php

<?php

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
{
    private tokenManager;
    privatevalidator;
    private accessor;
    privatelogger;

    public function __construct(CsrfTokenManagerInterface tokenManager,
        ValidatorInterfacevalidator, PropertyAccessorInterface accessor,
        LoggerInterfacelogger)
    {
        this->tokenManager =tokenManager;
        this->validator =validator;
        this->accessor =accessor;
        this->logger =logger;
    }

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

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

        return isValid;
    }

    public function validateInput(arrayinput): 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 asviolation) {

                this->accessor->setValue(messages,
                    violation->getPropertyPath(),violation->getMessage());
            }

            return $messages;
        } else {

            return [];
        }
    }
}

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

public function __construct(CsrfTokenManagerInterface tokenManager,
    ValidatorInterfacevalidator, PropertyAccessorInterface accessor,
    LoggerInterfacelogger)
{
    this->tokenManager =tokenManager;
    this->validator =validator;
    this->accessor =accessor;
    this->logger =logger;
}

我们在构造函数中注入了四个对象:令牌管理器,验证器,属性访问器和记录器。

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

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

    return $isValid;
}

此代码使用令牌管理器验证 CSRF 令牌。

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

    returnmessages;
} else {

    return [];
}

如果存在一些违规行为,我们将记录故障并生成验证错误消息。 为了构建消息,我们利用 Symfony 属性访问器。 如果没有违规,我们将返回一个空数组。

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="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')}}">
        </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')}}">
        </div>

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

    </form>

</section>

{% endblock %}

主页上有一个表格。 该表格包含两个字段:姓名和电子邮件。

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

它还包含一个隐藏字段,以防止跨站点请求伪造。

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

如果闪存包中有一些错误消息,我们将显示它们。

<input type="text" name="name" value="{{app.session.get('name')}}">

输入标签从会话中检索其值(如果有)。 重定向到表单后,这很有用。

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 模板。 它包含语义 UI CSS 框架。

您可能也对以下相关教程感兴趣: Symfony 简介Symfony 服务教程Symfony Flash 消息Symfony 表单教程PHP 教程

Python教程

Java教程

Web教程

数据库教程

图形图像教程

大数据教程

开发工具教程

计算机教程