PHP项目Symfony form与时间限制

wen PHP项目 3

本文目录导读:

PHP项目Symfony form与时间限制

  1. 表单验证中的时间限制
  2. 自定义时间限制验证器
  3. 表单时间限制实现
  4. JavaScript端时间限制
  5. 高级时间限制模式
  6. 数据库层面的时间限制
  7. 最佳实践

我来为你详细介绍PHP项目中使用Symfony Form组件时的时间限制处理。

表单验证中的时间限制

基本时间验证

// src/Form/Type/EventType.php
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Validator\Constraints as Assert;
use Symfony\Component\Form\Extension\Core\Type\DateTimeType;
class EventType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('startDate', DateTimeType::class, [
                'constraints' => [
                    new Assert\NotBlank(),
                    new Assert\DateTime(),
                    new Assert\GreaterThanOrEqual([
                        'value' => 'today',
                        'message' => '开始日期不能早于今天'
                    ])
                ]
            ])
            ->add('endDate', DateTimeType::class, [
                'constraints' => [
                    new Assert\NotBlank(),
                    new Assert\DateTime(),
                    new Assert\GreaterThan([
                        'propertyPath' => 'parent.all[startDate].data',
                        'message' => '结束日期必须晚于开始日期'
                    ])
                ]
            ]);
    }
}

自定义时间限制验证器

创建自定义约束

// src/Validator/Constraints/TimeLimit.php
namespace App\Validator\Constraints;
use Symfony\Component\Validator\Constraint;
/**
 * @Annotation
 */
class TimeLimit extends Constraint
{
    public $message = '操作时间已过,请重新提交表单。';
    public $minutes = 30;
    public $expiresAt = null;
    public function getRequiredOptions()
    {
        return ['minutes'];
    }
}

验证器实现

// src/Validator/Constraints/TimeLimitValidator.php
namespace App\Validator\Constraints;
use Symfony\Component\Validator\ConstraintValidator;
use Symfony\Component\HttpFoundation\RequestStack;
class TimeLimitValidator extends ConstraintValidator
{
    private $requestStack;
    public function __construct(RequestStack $requestStack)
    {
        $this->requestStack = $requestStack;
    }
    public function validate($value, Constraint $constraint)
    {
        if (!$constraint instanceof TimeLimit) {
            return;
        }
        $request = $this->requestStack->getCurrentRequest();
        $formStartTime = $request->getSession()->get('form_start_time');
        if (!$formStartTime) {
            return;
        }
        $elapsed = time() - $formStartTime;
        $limitSeconds = $constraint->minutes * 60;
        if ($elapsed > $limitSeconds) {
            $this->context->buildViolation($constraint->message)
                ->setParameter('{{ minutes }}', $constraint->minutes)
                ->addViolation();
        }
    }
}

表单时间限制实现

带时间限制的表单

// src/Form/Type/LimitedFormType.php
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormEvents;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\HttpFoundation\RequestStack;
class LimitedFormType extends AbstractType
{
    private $requestStack;
    private $timeLimit = 30; // 分钟
    public function __construct(RequestStack $requestStack)
    {
        $this->requestStack = $requestStack;
    }
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        // 在表单开始时记录时间
        $builder->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event) {
            $form = $event->getForm();
            $request = $this->requestStack->getCurrentRequest();
            if ($request->isMethod('GET')) {
                $request->getSession()->set('form_start_time', time());
            }
        });
        // 验证时间限制
        $builder->addEventListener(FormEvents::SUBMIT, function (FormEvent $event) {
            $form = $event->getForm();
            $request = $this->requestStack->getCurrentRequest();
            $startTime = $request->getSession()->get('form_start_time');
            if ($startTime && (time() - $startTime) > ($this->timeLimit * 60)) {
                $form->addError(new FormError('表单已过期,请重新提交'));
                $event->stopPropagation();
            }
        });
    }
}

JavaScript端时间限制

Twig模板集成

{# templates/form/time_limited.html.twig #}
{% extends 'base.html.twig' %}
{% block body %}
    {{ form_start(form, {'attr': {
        'id': 'time-limited-form',
        'data-time-limit': timeLimit,
        'data-expire-message': '表单已过期,请刷新页面'
    }}) }}
    <div class="form-timer" id="formTimer">
        剩余时间:<span id="countdown">{{ timeLimit }}:00</span>
    </div>
    {{ form_widget(form) }}
    <button type="submit" class="btn">提交</button>
    {{ form_end(form) }}
{% endblock %}
{% block javascripts %}
{{ parent() }}
<script>
document.addEventListener('DOMContentLoaded', function() {
    const form = document.getElementById('time-limited-form');
    const timerElement = document.getElementById('countdown');
    let timeLeft = {{ timeLimit }} * 60; // 秒
    const countdown = setInterval(() => {
        timeLeft--;
        const minutes = Math.floor(timeLeft / 60);
        const seconds = timeLeft % 60;
        timerElement.textContent = `${minutes}:${seconds.toString().padStart(2, '0')}`;
        if (timeLeft <= 0) {
            clearInterval(countdown);
            form.innerHTML = '<p>' + form.dataset.expireMessage + '</p>';
            // 可以选择自动刷新页面
            // setTimeout(() => location.reload(), 3000);
        }
    }, 1000);
    // 窗口关闭或刷新时清理
    window.addEventListener('beforeunload', function() {
        clearInterval(countdown);
    });
});
</script>
{% endblock %}

高级时间限制模式

Token-based时间限制

// src/Service/FormTokenService.php
namespace App\Service;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\Security\Csrf\TokenGenerator\TokenGeneratorInterface;
class FormTokenService
{
    private $requestStack;
    private $tokenGenerator;
    private $defaultExpiration = 3600; // 1小时
    public function __construct(
        RequestStack $requestStack,
        TokenGeneratorInterface $tokenGenerator
    ) {
        $this->requestStack = $requestStack;
        $this->tokenGenerator = $tokenGenerator;
    }
    public function generateFormToken(string $formName, int $expiration = null): array
    {
        $expiration = $expiration ?? $this->defaultExpiration;
        $token = $this->tokenGenerator->generateToken();
        $expiresAt = time() + $expiration;
        $session = $this->requestStack->getSession();
        $session->set("form_token_{$formName}", [
            'token' => $token,
            'expires_at' => $expiresAt
        ]);
        return [
            'token' => $token,
            'expires_at' => $expiresAt
        ];
    }
    public function validateFormToken(string $formName, string $submittedToken): bool
    {
        $session = $this->requestStack->getSession();
        $storedData = $session->get("form_token_{$formName}");
        if (!$storedData || !isset($storedData['token'])) {
            return false;
        }
        // 检查是否过期
        if ($storedData['expires_at'] < time()) {
            return false;
        }
        // 验证Token
        return hash_equals($storedData['token'], $submittedToken);
    }
}

在表单中使用Token

// src/Form/Type/SecureFormType.php
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\Extension\Core\Type\HiddenType;
use Symfony\Component\Validator\Constraints as Assert;
class SecureFormType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('form_token', HiddenType::class, [
                'mapped' => false,
                'constraints' => [
                    new Assert\NotBlank(),
                    // 自定义验证器
                    new Assert\Callback(function ($value, $context) {
                        $tokenService = $context->getValidator()
                            ->getMetadataFor('App\\Service\\FormTokenService');
                        if (!$tokenService->validateFormToken('secure_form', $value)) {
                            $context->buildViolation('表单已过期,请重新提交')
                                ->addViolation();
                        }
                    })
                ]
            ])
            // 其他字段...
        ;
    }
}

数据库层面的时间限制

Doctrine实体配置

// src/Entity/TimeLimitedEntity.php
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
 * @ORM\Entity
 * @ORM\HasLifecycleCallbacks
 */
class TimeLimitedEntity
{
    /**
     * @ORM\Column(type="datetime")
     */
    private $createdAt;
    /**
     * @ORM\Column(type="datetime", nullable=true)
     */
    private $expiresAt;
    /**
     * @ORM\Column(type="boolean")
     */
    private $isExpired = false;
    /**
     * @ORM\PrePersist
     */
    public function onPrePersist()
    {
        $this->createdAt = new \DateTime();
        if (!$this->expiresAt) {
            // 默认24小时后过期
            $this->expiresAt = new \DateTime('+24 hours');
        }
    }
    public function isExpired(): bool
    {
        if ($this->isExpired) {
            return true;
        }
        if ($this->expiresAt && $this->expiresAt < new \DateTime()) {
            $this->isExpired = true;
            return true;
        }
        return false;
    }
}

最佳实践

配置时间限制参数

# config/packages/form_time_limit.yaml
parameters:
    form_time_limit.default: 1800  # 30分钟默认
    form_time_limit.max: 7200      # 最大2小时
services:
    App\Form\TimeLimitExtension:
        tags:
            - { name: form.type_extension, extended_type: Symfony\Component\Form\Extension\Core\Type\FormType }

全局时间限制扩展

// src/Form/TimeLimitExtension.php
namespace App\Form;
use Symfony\Component\Form\AbstractTypeExtension;
use Symfony\Component\Form\Extension\Core\Type\FormType;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\Form\FormView;
use Symfony\Component\OptionsResolver\OptionsResolver;
class TimeLimitExtension extends AbstractTypeExtension
{
    private $defaultTimeLimit;
    public function __construct(int $defaultTimeLimit = 1800)
    {
        $this->defaultTimeLimit = $defaultTimeLimit;
    }
    public static function getExtendedTypes(): iterable
    {
        return [FormType::class];
    }
    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults([
            'time_limit' => $this->defaultTimeLimit
        ]);
        $resolver->setAllowedTypes('time_limit', ['int', 'bool']);
    }
    public function finishView(FormView $view, FormInterface $form, array $options)
    {
        if ($options['time_limit'] !== false) {
            $view->vars['time_limit'] = $options['time_limit'];
        }
    }
}

这些方法和实现可以帮助你在Symfony项目中高效地管理和处理表单的时间限制问题,根据具体需求选择合适的方法组合使用。

抱歉,评论功能暂时关闭!