PHP项目Symfony form与时间段选择

wen PHP项目 2

本文目录导读:

PHP项目Symfony form与时间段选择

  1. 使用日期时间选择器(最简单方案)
  2. 使用自定义时间段表单类型
  3. 带日期的时间段选择(推荐方案)
  4. 添加验证约束
  5. 前端集成(使用Flatpickr)
  6. 使用DTO(数据传输对象)
  7. 高级功能 - 时间段选择器组件
  8. 使用建议

在Symfony框架中处理时间段选择(如开始时间到结束时间)有多种常见方案,以下我会详细介绍几种实用的实现方式:

使用日期时间选择器(最简单方案)

实体类设计

// src/Entity/Event.php
class Event
{
    #[ORM\Id]
    #[ORM\GeneratedValue]
    #[ORM\Column]
    private ?int $id = null;
    #[ORM\Column(type: 'datetime')]
    private ?\DateTimeInterface $startTime = null;
    #[ORM\Column(type: 'datetime')]
    private ?\DateTimeInterface $endTime = null;
    // getters/setters...
}

Form Type

// src/Form/EventType.php
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\DateTimeType;
use Symfony\Component\Form\FormBuilderInterface;
class EventType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options): void
    {
        $builder
            ->add('startTime', DateTimeType::class, [
                'widget' => 'single_text',
                'html5' => true,
                'label' => '开始时间',
                'attr' => ['class' => 'datetime-picker'],
            ])
            ->add('endTime', DateTimeType::class, [
                'widget' => 'single_text',
                'html5' => true,
                'label' => '结束时间',
                'attr' => ['class' => 'datetime-picker'],
            ]);
    }
}

使用自定义时间段表单类型

创建时间段表单类型

// src/Form/Type/TimeRangeType.php
namespace App\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\TimeType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Validator\Constraints\Valid;
class TimeRangeType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options): void
    {
        $builder
            ->add('start', TimeType::class, [
                'widget' => 'single_text',
                'input' => 'datetime',
                'label' => '开始时间',
                'attr' => ['class' => 'time-input'],
            ])
            ->add('end', TimeType::class, [
                'widget' => 'single_text',
                'input' => 'datetime',
                'label' => '结束时间',
                'attr' => ['class' => 'time-input'],
            ]);
    }
    public function configureOptions(OptionsResolver $resolver): void
    {
        $resolver->setDefaults([
            'data_class' => null,
            'constraints' => [
                new Valid(),
            ],
        ]);
    }
}

实体中使用Embeddable

// src/Entity/TimeRange.php
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
#[ORM\Embeddable]
class TimeRange
{
    #[ORM\Column(type: 'time', nullable: true)]
    private ?\DateTimeInterface $start = null;
    #[ORM\Column(type: 'time', nullable: true)]
    private ?\DateTimeInterface $end = null;
    // getters/setters...
    public function getDuration(): ?\DateInterval
    {
        if ($this->start && $this->end) {
            return $this->start->diff($this->end);
        }
        return null;
    }
}
// 在实体中使用
class WorkSchedule
{
    #[ORM\Embedded(class: TimeRange::class)]
    private TimeRange $morningShift;
    #[ORM\Embedded(class: TimeRange::class)]
    private TimeRange $afternoonShift;
}

带日期的时间段选择(推荐方案)

创建完整的时间段表单类型

// src/Form/Type/DateTimeRangeType.php
namespace App\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\DateType;
use Symfony\Component\Form\Extension\Core\Type\TimeType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\Form\FormView;
use Symfony\Component\OptionsResolver\OptionsResolver;
class DateTimeRangeType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options): void
    {
        $builder
            ->add('startDate', DateType::class, [
                'widget' => 'single_text',
                'label' => '开始日期',
                'attr' => ['class' => 'datepicker'],
            ])
            ->add('startTime', TimeType::class, [
                'widget' => 'single_text',
                'label' => '开始时间',
                'attr' => ['class' => 'timepicker'],
            ])
            ->add('endDate', DateType::class, [
                'widget' => 'single_text',
                'label' => '结束日期',
                'attr' => ['class' => 'datepicker'],
            ])
            ->add('endTime', TimeType::class, [
                'widget' => 'single_text',
                'label' => '结束时间',
                'attr' => ['class' => 'timepicker'],
            ]);
    }
    public function configureOptions(OptionsResolver $resolver): void
    {
        $resolver->setDefaults([
            'data_class' => null,
            'allow_all_day' => false,
        ]);
    }
    public function buildView(FormView $view, FormInterface $form, array $options): void
    {
        $view->vars['allow_all_day'] = $options['allow_all_day'];
    }
}

添加验证约束

自定义验证器

// src/Validator/Constraints/ValidTimeRange.php
namespace App\Validator\Constraints;
use Symfony\Component\Validator\Constraint;
#[Attribute]
class ValidTimeRange extends Constraint
{
    public string $message = '结束时间必须晚于开始时间';
    public function getTargets(): string
    {
        return self::CLASS_CONSTRAINT;
    }
}
// src/Validator/ValidTimeRangeValidator.php
namespace App\Validator;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
class ValidTimeRangeValidator extends ConstraintValidator
{
    public function validate($value, Constraint $constraint): void
    {
        if (!$value instanceof TimeRangeDto && !is_array($value)) {
            return;
        }
        $start = is_array($value) ? ($value['start'] ?? null) : $value->getStart();
        $end = is_array($value) ? ($value['end'] ?? null) : $value->getEnd();
        if ($start && $end && $end <= $start) {
            $this->context->buildViolation($constraint->message)
                ->addViolation();
        }
    }
}
// 表单中使用验证
class EventType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options): void
    {
        $builder
            ->add('timeRange', DateTimeRangeType::class, [
                'constraints' => [
                    new ValidTimeRange(),
                ],
            ]);
    }
}

前端集成(使用Flatpickr)

安装依赖

npm install flatpickr

Twig模板

{# templates/event/new.html.twig #}
{% extends 'base.html.twig' %}
{% block javascripts %}
    {{ parent() }}
    <script src="https://cdn.jsdelivr.net/npm/flatpickr"></script>
    <script>
    document.addEventListener('DOMContentLoaded', function() {
        // 初始化日期时间选择器
        flatpickr('#event_startTime', {
            enableTime: true,
            dateFormat: "Y-m-d H:i",
            minDate: "today"
        });
        flatpickr('#event_endTime', {
            enableTime: true,
            dateFormat: "Y-m-d H:i",
            minDate: "today"
        });
        // 确保结束时间不小于开始时间
        const startInput = document.querySelector('#event_startTime');
        const endInput = document.querySelector('#event_endTime');
        startInput.addEventListener('change', function() {
            endInput._flatpickr.set('minDate', this.value);
        });
    });
    </script>
{% endblock %}

使用DTO(数据传输对象)

// src/DTO/TimeRangeDTO.php
namespace App\DTO;
use Symfony\Component\Validator\Constraints as Assert;
class TimeRangeDTO
{
    #[Assert\NotBlank]
    #[Assert\DateTime]
    public ?\DateTimeInterface $start = null;
    #[Assert\NotBlank]
    #[Assert\DateTime]
    #[Assert\GreaterThan(propertyPath: 'start', message: '结束时间必须晚于开始时间')]
    public ?\DateTimeInterface $end = null;
}
// 在控制器中使用
class EventController extends AbstractController
{
    public function new(Request $request): Response
    {
        $timeRange = new TimeRangeDTO();
        $form = $this->createFormBuilder($timeRange)
            ->add('start', DateTimeType::class, [
                'widget' => 'single_text',
                'label' => '开始时间',
            ])
            ->add('end', DateTimeType::class, [
                'widget' => 'single_text',
                'label' => '结束时间',
            ])
            ->add('save', SubmitType::class, ['label' => '保存'])
            ->getForm();
        $form->handleRequest($request);
        if ($form->isSubmitted() && $form->isValid()) {
            // 处理数据
            $startTime = $timeRange->start;
            $endTime = $timeRange->end;
            // 保存到数据库...
        }
        return $this->render('event/new.html.twig', [
            'form' => $form->createView(),
        ]);
    }
}

高级功能 - 时间段选择器组件

// src/Form/Type/TimeSlotType.php
namespace App\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\Extension\Core\Type\DateType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class TimeSlotType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options): void
    {
        $timeSlots = $this->generateTimeSlots($options['interval'], $options['start_hour'], $options['end_hour']);
        $builder
            ->add('startSlot', ChoiceType::class, [
                'choices' => $timeSlots,
                'label' => '开始时段',
                'placeholder' => '选择开始时间',
            ])
            ->add('endSlot', ChoiceType::class, [
                'choices' => $timeSlots,
                'label' => '结束时段',
                'placeholder' => '选择结束时间',
            ]);
    }
    private function generateTimeSlots(int $interval = 30, int $startHour = 8, int $endHour = 18): array
    {
        $slots = [];
        $start = new \DateTime("{$startHour}:00");
        $end = new \DateTime("{$endHour}:00");
        while ($start <= $end) {
            $time = $start->format('H:i');
            $slots[$time] = $time;
            $start->modify("+{$interval} minutes");
        }
        return $slots;
    }
    public function configureOptions(OptionsResolver $resolver): void
    {
        $resolver->setDefaults([
            'interval' => 30,
            'start_hour' => 8,
            'end_hour' => 18,
        ]);
    }
}

使用建议

  1. 简单场景:使用DateTimeType分别选择开始和结束时间
  2. 复杂场景:创建自定义表单类型+DTO
  3. 时间精度要求不高:使用时间段选择器(如30分钟间隔)
  4. 需要验证:添加自定义验证器或使用Assert注解
  5. 前端体验:集成Flatpickr、Tempus Dominus等JS库

选择哪种方案取决于你的具体需求:是否需要跨天、时间精度要求、UI设计等,对于大多数业务场景,方案3(带日期的时间段选择)搭配验证是最实用的选择。

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